diff options
Diffstat (limited to 'indra/llui')
135 files changed, 9454 insertions, 3041 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index e98201ea63..772f173f17 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -5,6 +5,7 @@ project(llui) include(00-Common) include(LLCommon) include(LLImage) +include(LLInventory) include(LLMath) include(LLMessage) include(LLRender) @@ -16,6 +17,7 @@ include(LLXUIXML) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} + ${LLINVENTORY_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} @@ -28,10 +30,14 @@ include_directories( set(llui_SOURCE_FILES llaccordionctrl.cpp llaccordionctrltab.cpp + llbadge.cpp + llbadgeholder.cpp + llbadgeowner.cpp llbutton.cpp llcheckboxctrl.cpp llclipboard.cpp llcombobox.cpp + llcommandmanager.cpp llconsole.cpp llcontainerview.cpp llctrlselectioninterface.cpp @@ -93,8 +99,10 @@ set(llui_SOURCE_FILES lltextparser.cpp lltextutil.cpp lltextvalidate.cpp + lltimectrl.cpp lltransutil.cpp lltoggleablemenu.cpp + lltoolbar.cpp lltooltip.cpp llui.cpp lluicolortable.cpp @@ -108,9 +116,11 @@ set(llui_SOURCE_FILES llurlmatch.cpp llurlregistry.cpp llviewborder.cpp + llviewinject.cpp llviewmodel.cpp llview.cpp llviewquery.cpp + llwindowshade.cpp ) set(llui_HEADER_FILES @@ -118,11 +128,15 @@ set(llui_HEADER_FILES llaccordionctrl.h llaccordionctrltab.h + llbadge.h + llbadgeholder.h + llbadgeowner.h llbutton.h llcallbackmap.h llcheckboxctrl.h llclipboard.h llcombobox.h + llcommandmanager.h llconsole.h llcontainerview.h llctrlselectioninterface.h @@ -159,6 +173,7 @@ set(llui_HEADER_FILES llnotificationslistener.h llnotificationsutil.h llnotificationtemplate.h + llnotificationvisibilityrule.h llpanel.h llprogressbar.h llradiogroup.h @@ -189,7 +204,9 @@ set(llui_HEADER_FILES lltextparser.h lltextutil.h lltextvalidate.h + lltimectrl.h lltoggleablemenu.h + lltoolbar.h lltooltip.h lltransutil.h lluicolortable.h @@ -206,9 +223,11 @@ set(llui_HEADER_FILES llurlmatch.h llurlregistry.h llviewborder.h + llviewinject.h llviewmodel.h llview.h llviewquery.h + llwindowshade.h ) set_source_files_properties(${llui_HEADER_FILES} @@ -234,6 +253,7 @@ target_link_libraries(llui ${LLRENDER_LIBRARIES} ${LLWINDOW_LIBRARIES} ${LLIMAGE_LIBRARIES} + ${LLINVENTORY_LIBRARIES} ${LLVFS_LIBRARIES} # ugh, just for LLDir ${LLXUIXML_LIBRARIES} ${LLXML_LIBRARIES} @@ -241,8 +261,8 @@ target_link_libraries(llui ${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender ) +# Add tests if(LL_TESTS) - # Add tests include(LLAddBuildTest) SET(llui_TEST_SOURCE_FILES llurlmatch.cpp diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp index 9d49c1a831..c025cd7939 100644 --- a/indra/llui/llaccordionctrltab.cpp +++ b/indra/llui/llaccordionctrltab.cpp @@ -203,7 +203,8 @@ void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::draw() S32 width = getRect().getWidth(); S32 height = getRect().getHeight(); - gl_rect_2d(0,0,width - 1 ,height - 1,mHeaderBGColor.get(),true); + F32 alpha = getCurrentTransparency(); + gl_rect_2d(0,0,width - 1 ,height - 1,mHeaderBGColor.get() % alpha,true); LLAccordionCtrlTab* parent = dynamic_cast<LLAccordionCtrlTab*>(getParent()); bool collapsible = (parent && parent->getCollapsible()); @@ -338,7 +339,7 @@ LLAccordionCtrlTab::Params::Params() ,fit_panel("fit_panel",true) ,selection_enabled("selection_enabled", false) { - mouse_opaque(false); + changeDefault(mouse_opaque, false); } LLAccordionCtrlTab::LLAccordionCtrlTab(const LLAccordionCtrlTab::Params&p) @@ -972,10 +973,10 @@ void LLAccordionCtrlTab::drawChild(const LLRect& root_rect,LLView* child) if ( root_rect.overlaps(screen_rect) && LLUI::sDirtyRect.overlaps(screen_rect)) { - glMatrixMode(GL_MODELVIEW); + gGL.matrixMode(LLRender::MM_MODELVIEW); LLUI::pushMatrix(); { - LLUI::translate((F32)child->getRect().mLeft, (F32)child->getRect().mBottom, 0.f); + LLUI::translate((F32)child->getRect().mLeft, (F32)child->getRect().mBottom); child->draw(); } @@ -1021,7 +1022,7 @@ void LLAccordionCtrlTab::updateLayout ( const LLRect& child_rect ) S32 panel_width = child_rect.getWidth(); static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); - if(mScrollbar->getVisible() != false) + if(mScrollbar && mScrollbar->getVisible() != false) { panel_top+=mScrollbar->getDocPos(); panel_width-=scrollbar_size; diff --git a/indra/llui/llbadge.cpp b/indra/llui/llbadge.cpp new file mode 100644 index 0000000000..8ede4e3468 --- /dev/null +++ b/indra/llui/llbadge.cpp @@ -0,0 +1,381 @@ +/** + * @file llbadge.cpp + * @brief Implementation for badges + * + * $LicenseInfo:firstyear=2001&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$ + */ + +#define LLBADGE_CPP +#include "llbadge.h" + +#include "llscrollcontainer.h" +#include "lluictrlfactory.h" + + +static LLDefaultChildRegistry::Register<LLBadge> r("badge"); + +static const S32 BADGE_OFFSET_NOT_SPECIFIED = 0x7FFFFFFF; + +// Compiler optimization, generate extern template +template class LLBadge* LLView::getChild<class LLBadge>(const std::string& name, BOOL recurse) const; + + +LLBadge::Params::Params() + : image("image") + , border_image("border_image") + , border_color("border_color") + , image_color("image_color") + , label("label") + , label_color("label_color") + , label_offset_horiz("label_offset_horiz") + , label_offset_vert("label_offset_vert") + , location("location", LLRelPos::TOP_LEFT) + , location_offset_hcenter("location_offset_hcenter") + , location_offset_vcenter("location_offset_vcenter") + , location_percent_hcenter("location_percent_hcenter") + , location_percent_vcenter("location_percent_vcenter") + , padding_horiz("padding_horiz") + , padding_vert("padding_vert") +{} + +bool LLBadge::Params::equals(const Params& a) const +{ + bool comp = true; + + // skip owner in comparison on purpose + + comp &= (border_image() == a.border_image()); + comp &= (border_color() == a.border_color()); + comp &= (image() == a.image()); + comp &= (image_color() == a.image_color()); + comp &= (label() == a.label()); + comp &= (label_color() == a.label_color()); + comp &= (label_offset_horiz() == a.label_offset_horiz()); + comp &= (label_offset_vert() == a.label_offset_vert()); + comp &= (location() == a.location()); + comp &= (location_offset_hcenter() == a.location_offset_hcenter()); + comp &= (location_offset_vcenter() == a.location_offset_vcenter()); + comp &= (location_percent_hcenter() == a.location_percent_hcenter()); + comp &= (location_percent_vcenter() == a.location_percent_vcenter()); + comp &= (padding_horiz() == a.padding_horiz()); + comp &= (padding_vert() == a.padding_vert()); + + return comp; +} + +LLBadge::LLBadge(const LLBadge::Params& p) + : LLUICtrl(p) + , mOwner(p.owner) + , mBorderImage(p.border_image) + , mBorderColor(p.border_color) + , mGLFont(p.font) + , mImage(p.image) + , mImageColor(p.image_color) + , mLabel(p.label) + , mLabelColor(p.label_color) + , mLabelOffsetHoriz(p.label_offset_horiz) + , mLabelOffsetVert(p.label_offset_vert) + , mLocation(p.location) + , mLocationOffsetHCenter(BADGE_OFFSET_NOT_SPECIFIED) + , mLocationOffsetVCenter(BADGE_OFFSET_NOT_SPECIFIED) + , mLocationPercentHCenter(0.5f) + , mLocationPercentVCenter(0.5f) + , mPaddingHoriz(p.padding_horiz) + , mPaddingVert(p.padding_vert) + , mParentScroller(NULL) +{ + if (mImage.isNull()) + { + llwarns << "Badge: " << getName() << " with no image!" << llendl; + } + + if (p.location_offset_hcenter.isProvided()) + { + mLocationOffsetHCenter = p.location_offset_hcenter(); + } + + if (p.location_offset_vcenter.isProvided()) + { + mLocationOffsetVCenter = p.location_offset_vcenter(); + } + + // + // The following logic is to set the mLocationPercentHCenter and mLocationPercentVCenter + // based on the Location enum and our horizontal and vertical location percentages. The + // draw code then uses this on the owner rectangle to compute the screen location for + // the badge. + // + + if (!LLRelPos::IsCenter(mLocation)) + { + F32 h_center = p.location_percent_hcenter * 0.01f; + F32 v_center = p.location_percent_vcenter * 0.01f; + + if (LLRelPos::IsRight(mLocation)) + { + mLocationPercentHCenter = 0.5f * (1.0f + h_center); + } + else if (LLRelPos::IsLeft(mLocation)) + { + mLocationPercentHCenter = 0.5f * (1.0f - h_center); + } + + if (LLRelPos::IsTop(mLocation)) + { + mLocationPercentVCenter = 0.5f * (1.0f + v_center); + } + else if (LLRelPos::IsBottom(mLocation)) + { + mLocationPercentVCenter = 0.5f * (1.0f - v_center); + } + } +} + +LLBadge::~LLBadge() +{ +} + +bool LLBadge::addToView(LLView * view) +{ + bool child_added = view->addChild(this); + + if (child_added) + { + setShape(view->getLocalRect()); + + // Find a parent scroll container, if there is one in case we need it for positioning + + LLView * parent = mOwner.get(); + + while ((parent != NULL) && ((mParentScroller = dynamic_cast<LLScrollContainer *>(parent)) == NULL)) + { + parent = parent->getParent(); + } + } + + return child_added; +} + +void LLBadge::setLabel(const LLStringExplicit& label) +{ + mLabel = label; +} + +// +// This is a fallback function to render a rectangle for badges without a valid image +// +void renderBadgeBackground(F32 centerX, F32 centerY, F32 width, F32 height, const LLColor4U &color) +{ + gGL.pushUIMatrix(); + gGL.loadUIIdentity(); + gGL.setSceneBlendType(LLRender::BT_REPLACE); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + gGL.color4ubv(color.mV); + gGL.texCoord2i(0, 0); + + F32 x = LLFontGL::sCurOrigin.mX + centerX - width * 0.5f; + F32 y = LLFontGL::sCurOrigin.mY + centerY - height * 0.5f; + + LLRectf screen_rect(llround(x), + llround(y), + llround(x) + width, + llround(y) + height); + + LLVector3 vertices[4]; + vertices[0] = LLVector3(screen_rect.mRight, screen_rect.mTop, 1.0f); + vertices[1] = LLVector3(screen_rect.mLeft, screen_rect.mTop, 1.0f); + vertices[2] = LLVector3(screen_rect.mLeft, screen_rect.mBottom, 1.0f); + vertices[3] = LLVector3(screen_rect.mRight, screen_rect.mBottom, 1.0f); + + gGL.begin(LLRender::QUADS); + { + gGL.vertexBatchPreTransformed(vertices, 4); + } + gGL.end(); + + gGL.popUIMatrix(); +} + + +// virtual +void LLBadge::draw() +{ + if (!mLabel.empty()) + { + LLView* owner_view = mOwner.get(); + + if (owner_view) + { + // + // Calculate badge size based on label text + // + + LLWString badge_label_wstring = mLabel; + + S32 badge_label_begin_offset = 0; + S32 badge_char_length = S32_MAX; + S32 badge_pixel_length = S32_MAX; + F32 *right_position_out = NULL; + BOOL do_not_use_ellipses = false; + + F32 badge_width = (2.0f * mPaddingHoriz) + + mGLFont->getWidthF32(badge_label_wstring.c_str(), badge_label_begin_offset, badge_char_length); + + F32 badge_height = (2.0f * mPaddingVert) + mGLFont->getLineHeight(); + + // + // Calculate badge position based on owner + // + + LLRect owner_rect; + owner_view->localRectToOtherView(owner_view->getLocalRect(), & owner_rect, this); + + S32 location_offset_horiz = mLocationOffsetHCenter; + S32 location_offset_vert = mLocationOffsetVCenter; + + // If we're in a scroll container, do some math to keep us in the same place on screen if applicable + if (mParentScroller != NULL) + { + LLRect visibleRect = mParentScroller->getVisibleContentRect(); + + if (mLocationOffsetHCenter != BADGE_OFFSET_NOT_SPECIFIED) + { + if (LLRelPos::IsRight(mLocation)) + { + location_offset_horiz += visibleRect.mRight; + } + else if (LLRelPos::IsLeft(mLocation)) + { + location_offset_horiz += visibleRect.mLeft; + } + else // center + { + location_offset_horiz += (visibleRect.mLeft + visibleRect.mRight) / 2; + } + } + + if (mLocationOffsetVCenter != BADGE_OFFSET_NOT_SPECIFIED) + { + if (LLRelPos::IsTop(mLocation)) + { + location_offset_vert += visibleRect.mTop; + } + else if (LLRelPos::IsBottom(mLocation)) + { + location_offset_vert += visibleRect.mBottom; + } + else // center + { + location_offset_vert += (visibleRect.mBottom + visibleRect.mTop) / 2; + } + } + } + + F32 badge_center_x; + F32 badge_center_y; + + // Compute x position + if (mLocationOffsetHCenter == BADGE_OFFSET_NOT_SPECIFIED) + { + badge_center_x = owner_rect.mLeft + owner_rect.getWidth() * mLocationPercentHCenter; + } + else + { + badge_center_x = location_offset_horiz; + } + + // Compute y position + if (mLocationOffsetVCenter == BADGE_OFFSET_NOT_SPECIFIED) + { + badge_center_y = owner_rect.mBottom + owner_rect.getHeight() * mLocationPercentVCenter; + } + else + { + badge_center_y = location_offset_vert; + } + + // + // Draw button image, if available. + // Otherwise draw basic rectangular button. + // + + F32 alpha = getDrawContext().mAlpha; + + if (!mImage.isNull()) + { + F32 badge_x = badge_center_x - badge_width * 0.5f; + F32 badge_y = badge_center_y - badge_height * 0.5f; + + mImage->drawSolid((S32) badge_x, (S32) badge_y, (S32) badge_width, (S32) badge_height, mImageColor % alpha); + + if (!mBorderImage.isNull()) + { + mBorderImage->drawSolid((S32) badge_x, (S32) badge_y, (S32) badge_width, (S32) badge_height, mBorderColor % alpha); + } + } + else + { + lldebugs << "No image for badge " << getName() << " on owner " << owner_view->getName() << llendl; + + renderBadgeBackground(badge_center_x, badge_center_y, + badge_width, badge_height, + mImageColor % alpha); + } + + // + // Draw the label + // + + mGLFont->render(badge_label_wstring, + badge_label_begin_offset, + badge_center_x + mLabelOffsetHoriz, + badge_center_y + mLabelOffsetVert, + mLabelColor % alpha, + LLFontGL::HCENTER, LLFontGL::VCENTER, // centered around the position + LLFontGL::NORMAL, // normal text (not bold, italics, etc.) + LLFontGL::DROP_SHADOW_SOFT, + badge_char_length, badge_pixel_length, + right_position_out, do_not_use_ellipses); + } + } +} + + +namespace LLInitParam +{ + void TypeValues<LLRelPos::Location>::declareValues() + { + declare("bottom", LLRelPos::BOTTOM); + declare("bottom_left", LLRelPos::BOTTOM_LEFT); + declare("bottom_right", LLRelPos::BOTTOM_RIGHT); + declare("center", LLRelPos::CENTER); + declare("left", LLRelPos::LEFT); + declare("right", LLRelPos::RIGHT); + declare("top", LLRelPos::TOP); + declare("top_left", LLRelPos::TOP_LEFT); + declare("top_right", LLRelPos::TOP_RIGHT); + } +} + + +// eof diff --git a/indra/llui/llbadge.h b/indra/llui/llbadge.h new file mode 100644 index 0000000000..4b21a71aaa --- /dev/null +++ b/indra/llui/llbadge.h @@ -0,0 +1,174 @@ +/** + * @file llbadge.h + * @brief Header for badges + * + * $LicenseInfo:firstyear=2001&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$ + */ + +#ifndef LL_LLBADGE_H +#define LL_LLBADGE_H + +#include <string> + +#include "lluicolor.h" +#include "lluictrl.h" +#include "llstring.h" +#include "lluiimage.h" +#include "llview.h" + +// +// Declarations +// + +class LLFontGL; +class LLScrollContainer; +class LLUICtrlFactory; + +// +// Relative Position Alignment +// + +namespace LLRelPos +{ + enum Location + { + CENTER = 0, + + LEFT = (1 << 0), + RIGHT = (1 << 1), + + TOP = (1 << 2), + BOTTOM = (1 << 3), + + BOTTOM_LEFT = (BOTTOM | LEFT), + BOTTOM_RIGHT = (BOTTOM | RIGHT), + + TOP_LEFT = (TOP | LEFT), + TOP_RIGHT = (TOP | RIGHT), + }; + + inline bool IsBottom(Location relPos) { return (relPos & BOTTOM) == BOTTOM; } + inline bool IsCenter(Location relPos) { return (relPos == CENTER); } + inline bool IsLeft(Location relPos) { return (relPos & LEFT) == LEFT; } + inline bool IsRight(Location relPos) { return (relPos & RIGHT) == RIGHT; } + inline bool IsTop(Location relPos) { return (relPos & TOP) == TOP; } +} + +// NOTE: This needs to occur before Optional<LLRelPos::Location> declaration for proper compilation. +namespace LLInitParam +{ + template<> + struct TypeValues<LLRelPos::Location> : public TypeValuesHelper<LLRelPos::Location> + { + static void declareValues(); + }; +} + +// +// Classes +// + +class LLBadge +: public LLUICtrl +{ +public: + struct Params + : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional< LLHandle<LLView> > owner; // Mandatory in code but not in xml + + Optional< LLUIImage* > border_image; + Optional< LLUIColor > border_color; + + Optional< LLUIImage* > image; + Optional< LLUIColor > image_color; + + Optional< std::string > label; + Optional< LLUIColor > label_color; + + Optional< S32 > label_offset_horiz; + Optional< S32 > label_offset_vert; + + Optional< LLRelPos::Location > location; + Optional< S32 > location_offset_hcenter; + Optional< S32 > location_offset_vcenter; + Optional< U32 > location_percent_hcenter; + Optional< U32 > location_percent_vcenter; + + Optional< F32 > padding_horiz; + Optional< F32 > padding_vert; + + Params(); + + bool equals(const Params&) const; + }; + +protected: + friend class LLUICtrlFactory; + LLBadge(const Params& p); + +public: + + ~LLBadge(); + + bool addToView(LLView * view); + + virtual void draw(); + + const std::string getLabel() const { return wstring_to_utf8str(mLabel); } + void setLabel( const LLStringExplicit& label); + +private: + LLPointer< LLUIImage > mBorderImage; + LLUIColor mBorderColor; + + const LLFontGL* mGLFont; + + LLPointer< LLUIImage > mImage; + LLUIColor mImageColor; + + LLUIString mLabel; + LLUIColor mLabelColor; + + S32 mLabelOffsetHoriz; + S32 mLabelOffsetVert; + + LLRelPos::Location mLocation; + S32 mLocationOffsetHCenter; + S32 mLocationOffsetVCenter; + F32 mLocationPercentHCenter; + F32 mLocationPercentVCenter; + + LLHandle< LLView > mOwner; + + F32 mPaddingHoriz; + F32 mPaddingVert; + + LLScrollContainer* mParentScroller; +}; + +// Build time optimization, generate once in .cpp file +#ifndef LLBADGE_CPP +extern template class LLBadge* LLView::getChild<class LLBadge>(const std::string& name, BOOL recurse) const; +#endif + +#endif // LL_LLBADGE_H diff --git a/indra/llui/llbadgeholder.cpp b/indra/llui/llbadgeholder.cpp new file mode 100644 index 0000000000..1f786f36ae --- /dev/null +++ b/indra/llui/llbadgeholder.cpp @@ -0,0 +1,45 @@ +/** + * @file llbadgeholder.cpp + * @brief Source for badge holders + * + * $LicenseInfo:firstyear=2001&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 "llbadgeholder.h" + +#include "llbadge.h" +#include "llview.h" + + +bool LLBadgeHolder::addBadge(LLBadge * badge) +{ + bool badge_added = false; + + LLView * this_view = dynamic_cast<LLView *>(this); + + if (this_view && mAcceptsBadge) + { + badge_added = badge->addToView(this_view); + } + + return badge_added; +} diff --git a/indra/llui/llbadgeholder.h b/indra/llui/llbadgeholder.h new file mode 100644 index 0000000000..2538eaae91 --- /dev/null +++ b/indra/llui/llbadgeholder.h @@ -0,0 +1,56 @@ +/** + * @file llbadgeholder.h + * @brief Header for badge holders + * + * $LicenseInfo:firstyear=2001&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$ + */ + +#ifndef LL_LLBADGEHOLDER_H +#define LL_LLBADGEHOLDER_H + +// +// Classes +// + +class LLBadge; + +class LLBadgeHolder +{ +public: + + LLBadgeHolder(bool acceptsBadge) + : mAcceptsBadge(acceptsBadge) + { + } + + void setAcceptsBadge(bool acceptsBadge) { mAcceptsBadge = acceptsBadge; } + bool acceptsBadge() const { return mAcceptsBadge; } + + virtual bool addBadge(LLBadge * badge); + +private: + + bool mAcceptsBadge; + +}; + +#endif // LL_LLBADGEHOLDER_H diff --git a/indra/llui/llbadgeowner.cpp b/indra/llui/llbadgeowner.cpp new file mode 100644 index 0000000000..1860a05edd --- /dev/null +++ b/indra/llui/llbadgeowner.cpp @@ -0,0 +1,131 @@ +/** + * @file llbadgeowner.cpp + * @brief Class to manage badges attached to a UI control + * + * $LicenseInfo:firstyear=2001&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 "linden_common.h" + +#include "llbadgeholder.h" +#include "llbadgeowner.h" +#include "llpanel.h" + +// +// Classes +// + +LLBadgeOwner::LLBadgeOwner(LLHandle< LLView > viewHandle) + : mBadge(NULL) + , mBadgeOwnerView(viewHandle) +{ +} + +void LLBadgeOwner::initBadgeParams(const LLBadge::Params& p) +{ + if (!p.equals(LLUICtrlFactory::getDefaultParams<LLBadge>())) + { + mBadge = createBadge(p); + } +} + +void LLBadgeOwner::setBadgeLabel(const LLStringExplicit& label) +{ + if (mBadge == NULL) + { + mBadge = createBadge(LLUICtrlFactory::getDefaultParams<LLBadge>()); + + addBadgeToParentPanel(); + } + + if (mBadge) + { + mBadge->setLabel(label); + + // + // Push the badge to the front so it renders on top + // + + LLView * parent = mBadge->getParent(); + + if (parent) + { + parent->sendChildToFront(mBadge); + } + } +} + +void LLBadgeOwner::setBadgeVisibility(bool visible) +{ + if (mBadge) + { + mBadge->setVisible(visible); + } +} + +bool LLBadgeOwner::addBadgeToParentPanel() +{ + bool badge_added = false; + + LLView * owner_view = mBadgeOwnerView.get(); + + if (mBadge && owner_view) + { + LLBadgeHolder * badge_holder = NULL; + + // Find the appropriate holder for the badge + LLView * parent = owner_view->getParent(); + + while (parent) + { + LLBadgeHolder * badge_holder_panel = dynamic_cast<LLBadgeHolder *>(parent); + + if (badge_holder_panel && badge_holder_panel->acceptsBadge()) + { + badge_holder = badge_holder_panel; + break; + } + + parent = parent->getParent(); + } + + if (badge_holder) + { + badge_added = badge_holder->addBadge(mBadge); + } + else + { + // Badge parent is fallback badge owner if no valid holder exists in the hierarchy + badge_added = mBadge->addToView(owner_view); + } + } + + return badge_added; +} + +LLBadge* LLBadgeOwner::createBadge(const LLBadge::Params& p) +{ + LLBadge::Params badge_params(p); + badge_params.owner = mBadgeOwnerView; + + return LLUICtrlFactory::create<LLBadge>(badge_params); +} diff --git a/indra/llui/llbadgeowner.h b/indra/llui/llbadgeowner.h new file mode 100644 index 0000000000..8d03e30645 --- /dev/null +++ b/indra/llui/llbadgeowner.h @@ -0,0 +1,61 @@ +/** + * @file llbadgeowner.h + * @brief Header for badge owners + * + * $LicenseInfo:firstyear=2001&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$ + */ + +#ifndef LL_LLBADGEOWNER_H +#define LL_LLBADGEOWNER_H + +#include "llbadge.h" +#include "llview.h" + +// +// Classes +// + +class LLBadgeOwner +{ +public: + + LLBadgeOwner(LLHandle< LLView > viewHandle); + + void initBadgeParams(const LLBadge::Params& p); + bool addBadgeToParentPanel(); + + bool badgeHasParent() const { return (mBadge && mBadge->getParent()); } + + void setBadgeLabel(const LLStringExplicit& label); + void setBadgeVisibility(bool visible); + +private: + + LLBadge* createBadge(const LLBadge::Params& p); + +private: + + LLBadge* mBadge; + LLHandle< LLView > mBadgeOwnerView; +}; + +#endif // LL_LLBADGEOWNER_H diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 65ef3e5f8f..705fe16559 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -83,10 +83,11 @@ LLButton::Params::Params() label_color_selected("label_color_selected"), // requires is_toggle true label_color_disabled("label_color_disabled"), label_color_disabled_selected("label_color_disabled_selected"), - highlight_color("highlight_color"), image_color("image_color"), image_color_disabled("image_color_disabled"), - image_overlay_color("image_overlay_color", LLColor4::white), + image_overlay_color("image_overlay_color", LLColor4::white % 0.75f), + image_overlay_disabled_color("image_overlay_disabled_color", LLColor4::white % 0.3f), + image_overlay_selected_color("image_overlay_selected_color", LLColor4::white), flash_color("flash_color"), pad_right("pad_right", LLUI::sSettingGroups["config"]->getS32("ButtonHPad")), pad_left("pad_left", LLUI::sSettingGroups["config"]->getS32("ButtonHPad")), @@ -98,16 +99,23 @@ LLButton::Params::Params() is_toggle("is_toggle", false), scale_image("scale_image", true), hover_glow_amount("hover_glow_amount"), - commit_on_return("commit_on_return", true) + commit_on_return("commit_on_return", true), + display_pressed_state("display_pressed_state", true), + use_draw_context_alpha("use_draw_context_alpha", true), + badge("badge"), + handle_right_mouse("handle_right_mouse"), + held_down_delay("held_down_delay"), + button_flash_count("button_flash_count"), + button_flash_rate("button_flash_rate") { addSynonym(is_toggle, "toggle"); - held_down_delay.seconds = 0.5f; - initial_value.set(LLSD(false), false); + changeDefault(initial_value, LLSD(false)); } LLButton::LLButton(const LLButton::Params& p) : LLUICtrl(p), + LLBadgeOwner(getHandle()), mMouseDownFrame(0), mMouseHeldDownCount(0), mBorderEnabled( FALSE ), @@ -132,12 +140,13 @@ LLButton::LLButton(const LLButton::Params& p) mSelectedLabelColor(p.label_color_selected()), mDisabledLabelColor(p.label_color_disabled()), mDisabledSelectedLabelColor(p.label_color_disabled_selected()), - mHighlightColor(p.highlight_color()), mImageColor(p.image_color()), mFlashBgColor(p.flash_color()), mDisabledImageColor(p.image_color_disabled()), mImageOverlay(p.image_overlay()), mImageOverlayColor(p.image_overlay_color()), + mImageOverlayDisabledColor(p.image_overlay_disabled_color()), + mImageOverlaySelectedColor(p.image_overlay_selected_color()), mImageOverlayAlignment(LLFontGL::hAlignFromName(p.image_overlay_alignment)), mImageOverlayTopPad(p.image_top_pad), mImageOverlayBottomPad(p.image_bottom_pad), @@ -155,11 +164,15 @@ LLButton::LLButton(const LLButton::Params& p) mCommitOnReturn(p.commit_on_return), mFadeWhenDisabled(FALSE), mForcePressedState(false), + mDisplayPressedState(p.display_pressed_state), mLastDrawCharsCount(0), mMouseDownSignal(NULL), mMouseUpSignal(NULL), - mHeldDownSignal(NULL) - + mHeldDownSignal(NULL), + mUseDrawContextAlpha(p.use_draw_context_alpha), + mHandleRightMouse(p.handle_right_mouse), + mButtonFlashCount(p.button_flash_count), + mButtonFlashRate(p.button_flash_rate) { static LLUICachedControl<S32> llbutton_orig_h_pad ("UIButtonOrigHPad", 0); static Params default_params(LLUICtrlFactory::getDefaultParams<LLButton>()); @@ -242,6 +255,11 @@ LLButton::LLButton(const LLButton::Params& p) { setHeldDownCallback(initCommitCallback(p.mouse_held_callback)); } + + if (p.badge.isProvided()) + { + LLBadgeOwner::initBadgeParams(p.badge()); + } } LLButton::~LLButton() @@ -282,6 +300,24 @@ void LLButton::onCommit() LLUICtrl::onCommit(); } +boost::signals2::connection LLButton::setClickedCallback(const CommitCallbackParam& cb) +{ + return setClickedCallback(initCommitCallback(cb)); +} +boost::signals2::connection LLButton::setMouseDownCallback(const CommitCallbackParam& cb) +{ + return setMouseDownCallback(initCommitCallback(cb)); +} +boost::signals2::connection LLButton::setMouseUpCallback(const CommitCallbackParam& cb) +{ + return setMouseUpCallback(initCommitCallback(cb)); +} +boost::signals2::connection LLButton::setHeldDownCallback(const CommitCallbackParam& cb) +{ + return setHeldDownCallback(initCommitCallback(cb)); +} + + boost::signals2::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb ) { if (!mCommitSignal) mCommitSignal = new commit_signal_t(); @@ -304,7 +340,7 @@ boost::signals2::connection LLButton::setHeldDownCallback( const commit_signal_t } -// *TODO: Deprecate (for backwards compatability only) +// *TODO: Deprecate (for backwards compatibility only) boost::signals2::connection LLButton::setClickedCallback( button_callback_t cb, void* data ) { return setClickedCallback(boost::bind(cb, data)); @@ -325,8 +361,12 @@ boost::signals2::connection LLButton::setHeldDownCallback( button_callback_t cb, BOOL LLButton::postBuild() { autoResize(); - return TRUE; + + addBadgeToParentPanel(); + + return LLUICtrl::postBuild(); } + BOOL LLButton::handleUnicodeCharHere(llwchar uni_char) { BOOL handled = FALSE; @@ -445,7 +485,7 @@ BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) BOOL LLButton::handleRightMouseDown(S32 x, S32 y, MASK mask) { - if (!childrenHandleRightMouseDown(x, y, mask)) + if (mHandleRightMouse && !childrenHandleRightMouseDown(x, y, mask)) { // Route future Mouse messages here preemptively. (Release on mouse up.) gFocusMgr.setMouseCapture( this ); @@ -458,49 +498,45 @@ BOOL LLButton::handleRightMouseDown(S32 x, S32 y, MASK mask) // if (pointInView(x, y)) // { // } + // send the mouse down signal + LLUICtrl::handleRightMouseDown(x,y,mask); + // *TODO: Return result of LLUICtrl call above? Should defer to base class + // but this might change the mouse handling of existing buttons in a bad way + // if they are not mouse opaque. } - // send the mouse down signal - LLUICtrl::handleRightMouseDown(x,y,mask); - // *TODO: Return result of LLUICtrl call above? Should defer to base class - // but this might change the mouse handling of existing buttons in a bad way - // if they are not mouse opaque. + return TRUE; } BOOL LLButton::handleRightMouseUp(S32 x, S32 y, MASK mask) { - // We only handle the click if the click both started and ended within us - if( hasMouseCapture() ) + if (mHandleRightMouse) { - // Always release the mouse - gFocusMgr.setMouseCapture( NULL ); + // We only handle the click if the click both started and ended within us + if( hasMouseCapture() ) + { + // Always release the mouse + gFocusMgr.setMouseCapture( NULL ); -// if (pointInView(x, y)) -// { -// mRightMouseUpSignal(this, x,y,mask); -// } - } - else - { - childrenHandleRightMouseUp(x, y, mask); + // if (pointInView(x, y)) + // { + // mRightMouseUpSignal(this, x,y,mask); + // } + } + else + { + childrenHandleRightMouseUp(x, y, mask); + } + + // send the mouse up signal + LLUICtrl::handleRightMouseUp(x,y,mask); + // *TODO: Return result of LLUICtrl call above? Should defer to base class + // but this might change the mouse handling of existing buttons in a bad way. + // if they are not mouse opaque. } - // send the mouse up signal - LLUICtrl::handleRightMouseUp(x,y,mask); - // *TODO: Return result of LLUICtrl call above? Should defer to base class - // but this might change the mouse handling of existing buttons in a bad way. - // if they are not mouse opaque. return TRUE; } - -void LLButton::onMouseEnter(S32 x, S32 y, MASK mask) -{ - LLUICtrl::onMouseEnter(x, y, mask); - - if (isInEnabledChain()) - mNeedsHighlight = TRUE; -} - void LLButton::onMouseLeave(S32 x, S32 y, MASK mask) { LLUICtrl::onMouseLeave(x, y, mask); @@ -515,6 +551,10 @@ void LLButton::setHighlight(bool b) BOOL LLButton::handleHover(S32 x, S32 y, MASK mask) { + if (isInEnabledChain() + && (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this)) + mNeedsHighlight = TRUE; + if (!childrenHandleHover(x, y, mask)) { if (mMouseDownTimer.getStarted()) @@ -535,21 +575,37 @@ BOOL LLButton::handleHover(S32 x, S32 y, MASK mask) return TRUE; } +void LLButton::getOverlayImageSize(S32& overlay_width, S32& overlay_height) +{ + overlay_width = mImageOverlay->getWidth(); + overlay_height = mImageOverlay->getHeight(); + + F32 scale_factor = llmin((F32)getRect().getWidth() / (F32)overlay_width, (F32)getRect().getHeight() / (F32)overlay_height, 1.f); + overlay_width = llround((F32)overlay_width * scale_factor); + overlay_height = llround((F32)overlay_height * scale_factor); +} + // virtual void LLButton::draw() { - F32 alpha = getDrawContext().mAlpha; + static LLCachedControl<bool> sEnableButtonFlashing(*LLUI::sSettingGroups["config"], "EnableButtonFlashing", true); + F32 alpha = mUseDrawContextAlpha ? getDrawContext().mAlpha : getCurrentTransparency(); bool flash = FALSE; - static LLUICachedControl<F32> button_flash_rate("ButtonFlashRate", 0); - static LLUICachedControl<S32> button_flash_count("ButtonFlashCount", 0); - if( mFlashing ) + if( mFlashing) { - F32 elapsed = mFlashingTimer.getElapsedTimeF32(); - S32 flash_count = S32(elapsed * button_flash_rate * 2.f); - // flash on or off? - flash = (flash_count % 2 == 0) || flash_count > S32((F32)button_flash_count * 2.f); + if ( sEnableButtonFlashing) + { + F32 elapsed = mFlashingTimer.getElapsedTimeF32(); + S32 flash_count = S32(elapsed * mButtonFlashRate * 2.f); + // flash on or off? + flash = (flash_count % 2 == 0) || flash_count > S32((F32)mButtonFlashCount * 2.f); + } + else + { // otherwise just highlight button in flash color + flash = true; + } } bool pressed_by_keyboard = FALSE; @@ -578,7 +634,7 @@ void LLButton::draw() LLColor4 glow_color = LLColor4::white; LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA; LLUIImage* imagep = NULL; - if (pressed) + if (pressed && mDisplayPressedState) { imagep = selected ? mImagePressedSelected : mImagePressed; } @@ -688,16 +744,7 @@ void LLButton::draw() } // Unselected label assignments - LLWString label; - - if( getToggleState() ) - { - label = mSelectedLabel; - } - else - { - label = mUnselectedLabel; - } + LLWString label = getCurrentLabel(); // overlay with keyboard focus border if (hasFocus()) @@ -762,18 +809,16 @@ void LLButton::draw() if (mImageOverlay.notNull()) { // get max width and height (discard level 0) - S32 overlay_width = mImageOverlay->getWidth(); - S32 overlay_height = mImageOverlay->getHeight(); + S32 overlay_width; + S32 overlay_height; - F32 scale_factor = llmin((F32)getRect().getWidth() / (F32)overlay_width, (F32)getRect().getHeight() / (F32)overlay_height, 1.f); - overlay_width = llround((F32)overlay_width * scale_factor); - overlay_height = llround((F32)overlay_height * scale_factor); + getOverlayImageSize(overlay_width, overlay_height); S32 center_x = getLocalRect().getCenterX(); S32 center_y = getLocalRect().getCenterY(); //FUGLY HACK FOR "DEPRESSED" BUTTONS - if (pressed) + if (pressed && mDisplayPressedState) { center_y--; center_x++; @@ -784,7 +829,11 @@ void LLButton::draw() LLColor4 overlay_color = mImageOverlayColor.get(); if (!enabled) { - overlay_color.mV[VALPHA] = 0.5f; + overlay_color = mImageOverlayDisabledColor.get(); + } + else if (getToggleState()) + { + overlay_color = mImageOverlaySelectedColor.get(); } overlay_color.mV[VALPHA] *= alpha; @@ -792,6 +841,7 @@ void LLButton::draw() { case LLFontGL::LEFT: text_left += overlay_width + mImgOverlayLabelSpace; + text_width -= overlay_width + mImgOverlayLabelSpace; mImageOverlay->draw( mLeftHPad, center_y - (overlay_height / 2), @@ -809,6 +859,7 @@ void LLButton::draw() break; case LLFontGL::RIGHT: text_right -= overlay_width + mImgOverlayLabelSpace; + text_width -= overlay_width + mImgOverlayLabelSpace; mImageOverlay->draw( getRect().getWidth() - mRightHPad - overlay_width, center_y - (overlay_height / 2), @@ -844,7 +895,7 @@ void LLButton::draw() S32 y_offset = 2 + (getRect().getHeight() - 20)/2; - if (pressed) + if (pressed && mDisplayPressedState) { y_offset--; x++; @@ -857,9 +908,9 @@ void LLButton::draw() // Not sure if it is really needed. Probably S32_MAX should be always passed as max_chars. mLastDrawCharsCount = mGLFont->render(label, 0, (F32)x, - (F32)(mBottomVPad + y_offset), + (F32)(getRect().getHeight() / 2 + mBottomVPad), label_color % alpha, - mHAlign, LLFontGL::BOTTOM, + mHAlign, LLFontGL::VCENTER, LLFontGL::NORMAL, mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW, S32_MAX, text_width, @@ -900,7 +951,7 @@ void LLButton::setToggleState(BOOL b) void LLButton::setFlashing( BOOL b ) { - if (b != mFlashing) + if ((bool)b != mFlashing) { mFlashing = b; mFlashingTimer.reset(); @@ -940,6 +991,23 @@ void LLButton::setLabelSelected( const LLStringExplicit& label ) mSelectedLabel = label; } +bool LLButton::labelIsTruncated() const +{ + return getCurrentLabel().getString().size() > mLastDrawCharsCount; +} + +const LLUIString& LLButton::getCurrentLabel() const +{ + if( getToggleState() ) + { + return mSelectedLabel; + } + else + { + return mUnselectedLabel; + } +} + void LLButton::setImageUnselected(LLPointer<LLUIImage> image) { mImageUnselected = image; @@ -951,16 +1019,7 @@ void LLButton::setImageUnselected(LLPointer<LLUIImage> image) void LLButton::autoResize() { - LLUIString label; - if(getToggleState()) - { - label = mSelectedLabel; - } - else - { - label = mUnselectedLabel; - } - resize(label); + resize(getCurrentLabel()); } void LLButton::resize(LLUIString label) @@ -970,11 +1029,32 @@ void LLButton::resize(LLUIString label) // get current btn length S32 btn_width =getRect().getWidth(); // check if it need resize - if (mAutoResize == TRUE) + if (mAutoResize) { - if (btn_width - (mRightHPad + mLeftHPad) < label_width) + S32 min_width = label_width + mLeftHPad + mRightHPad; + if (mImageOverlay) + { + S32 overlay_width = mImageOverlay->getWidth(); + F32 scale_factor = (getRect().getHeight() - (mImageOverlayBottomPad + mImageOverlayTopPad)) / (F32)mImageOverlay->getHeight(); + overlay_width = llround((F32)overlay_width * scale_factor); + + switch(mImageOverlayAlignment) + { + case LLFontGL::LEFT: + case LLFontGL::RIGHT: + min_width += overlay_width + mImgOverlayLabelSpace; + break; + case LLFontGL::HCENTER: + min_width = llmax(min_width, overlay_width + mLeftHPad + mRightHPad); + break; + default: + // draw nothing + break; + } + } + if (btn_width < min_width) { - setRect(LLRect( getRect().mLeft, getRect().mTop, getRect().mLeft + label_width + mLeftHPad + mRightHPad , getRect().mBottom)); + reshape(min_width, getRect().getHeight()); } } } @@ -1121,7 +1201,7 @@ void LLButton::setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); // Set the clicked callback to toggle the floater - button->setClickedCallback(boost::bind(&LLFloaterReg::toggleFloaterInstance, sdname)); + button->setClickedCallback(boost::bind(&LLFloaterReg::toggleInstance, sdname, LLSD())); } // static @@ -1162,7 +1242,6 @@ void LLButton::resetMouseDownTimer() mMouseDownTimer.reset(); } - BOOL LLButton::handleDoubleClick(S32 x, S32 y, MASK mask) { // just treat a double click as a second click diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 2d5fefa78c..deaa0823c6 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -28,6 +28,7 @@ #define LL_LLBUTTON_H #include "lluuid.h" +#include "llbadgeowner.h" #include "llcontrol.h" #include "lluictrl.h" #include "v4color.h" @@ -59,7 +60,7 @@ class LLUICtrlFactory; // class LLButton -: public LLUICtrl +: public LLUICtrl, public LLBadgeOwner { public: struct Params @@ -90,10 +91,11 @@ public: label_color_selected, label_color_disabled, label_color_disabled_selected, - highlight_color, image_color, image_color_disabled, image_overlay_color, + image_overlay_selected_color, + image_overlay_disabled_color, flash_color; // layout @@ -119,11 +121,21 @@ public: // misc Optional<bool> is_toggle, scale_image, - commit_on_return; + commit_on_return, + display_pressed_state; Optional<F32> hover_glow_amount; Optional<TimeIntervalParam> held_down_delay; + Optional<bool> use_draw_context_alpha; + + Optional<LLBadge::Params> badge; + + Optional<bool> handle_right_mouse; + + Optional<S32> button_flash_count; + Optional<F32> button_flash_rate; + Params(); }; @@ -150,7 +162,6 @@ public: virtual void draw(); /*virtual*/ BOOL postBuild(); - virtual void onMouseEnter(S32 x, S32 y, MASK mask); virtual void onMouseLeave(S32 x, S32 y, MASK mask); virtual void onMouseCaptureLost(); @@ -161,6 +172,11 @@ public: void setUseEllipses( BOOL use_ellipses ) { mUseEllipses = use_ellipses; } + boost::signals2::connection setClickedCallback(const CommitCallbackParam& cb); + boost::signals2::connection setMouseDownCallback(const CommitCallbackParam& cb); + boost::signals2::connection setMouseUpCallback(const CommitCallbackParam& cb); + boost::signals2::connection setHeldDownCallback(const CommitCallbackParam& cb); + boost::signals2::connection setClickedCallback( const commit_signal_t::slot_type& cb ); // mouse down and up within button boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); // mouse up, EVEN IF NOT IN BUTTON @@ -228,6 +244,8 @@ public: S32 getLastDrawCharsCount() const { return mLastDrawCharsCount; } + bool labelIsTruncated() const; + const LLUIString& getCurrentLabel() const; void setScaleImage(BOOL scale) { mScaleImage = scale; } BOOL getScaleImage() const { return mScaleImage; } @@ -246,7 +264,7 @@ public: void setImageDisabledSelected(LLPointer<LLUIImage> image); void setImageFlash(LLPointer<LLUIImage> image); void setImagePressed(LLPointer<LLUIImage> image); - + void setCommitOnReturn(BOOL commit) { mCommitOnReturn = commit; } BOOL getCommitOnReturn() const { return mCommitOnReturn; } @@ -263,14 +281,16 @@ public: protected: LLPointer<LLUIImage> getImageUnselected() const { return mImageUnselected; } LLPointer<LLUIImage> getImageSelected() const { return mImageSelected; } + void getOverlayImageSize(S32& overlay_width, S32& overlay_height); LLFrameTimer mMouseDownTimer; + bool mNeedsHighlight; + S32 mButtonFlashCount; + F32 mButtonFlashRate; -private: void drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size); void resetMouseDownTimer(); -private: commit_signal_t* mMouseDownSignal; commit_signal_t* mMouseUpSignal; commit_signal_t* mHeldDownSignal; @@ -286,6 +306,8 @@ private: LLPointer<LLUIImage> mImageOverlay; LLFontGL::HAlign mImageOverlayAlignment; LLUIColor mImageOverlayColor; + LLUIColor mImageOverlaySelectedColor; + LLUIColor mImageOverlayDisabledColor; LLPointer<LLUIImage> mImageUnselected; LLUIString mUnselectedLabel; @@ -314,21 +336,19 @@ private: flash icon name is set in attributes(by default it isn't). First way is used otherwise. */ LLPointer<LLUIImage> mImageFlash; - LLUIColor mHighlightColor; LLUIColor mFlashBgColor; LLUIColor mImageColor; LLUIColor mDisabledImageColor; - BOOL mIsToggle; - BOOL mScaleImage; - - BOOL mDropShadowedText; - BOOL mAutoResize; - BOOL mUseEllipses; - BOOL mBorderEnabled; + bool mIsToggle; + bool mScaleImage; - BOOL mFlashing; + bool mDropShadowedText; + bool mAutoResize; + bool mUseEllipses; + bool mBorderEnabled; + bool mFlashing; LLFontGL::HAlign mHAlign; S32 mLeftHPad; @@ -338,6 +358,8 @@ private: S32 mImageOverlayTopPad; S32 mImageOverlayBottomPad; + bool mUseDrawContextAlpha; + /* * Space between image_overlay and label */ @@ -346,12 +368,14 @@ private: F32 mHoverGlowStrength; F32 mCurGlowStrength; - BOOL mNeedsHighlight; - BOOL mCommitOnReturn; - BOOL mFadeWhenDisabled; + bool mCommitOnReturn; + bool mFadeWhenDisabled; bool mForcePressedState; + bool mDisplayPressedState; LLFrameTimer mFlashingTimer; + + bool mHandleRightMouse; }; // Build time optimization, generate once in .cpp file diff --git a/indra/llui/llcheckboxctrl.cpp b/indra/llui/llcheckboxctrl.cpp index bbd8db2645..4fe444c1a4 100644 --- a/indra/llui/llcheckboxctrl.cpp +++ b/indra/llui/llcheckboxctrl.cpp @@ -88,27 +88,19 @@ LLCheckBoxCtrl::LLCheckBoxCtrl(const LLCheckBoxCtrl::Params& p) tbparams.font(p.font); } mLabel = LLUICtrlFactory::create<LLTextBox> (tbparams); + mLabel->reshapeToFitText(); addChild(mLabel); - S32 text_width = mLabel->getTextBoundingRect().getWidth(); - S32 text_height = llround(mFont->getLineHeight()); - LLRect label_rect; - label_rect.setOriginAndSize( - llcheckboxctrl_hpad + llcheckboxctrl_btn_size + llcheckboxctrl_spacing, - llcheckboxctrl_vpad + 1, // padding to get better alignment - text_width + llcheckboxctrl_hpad, - text_height ); - mLabel->setShape(label_rect); - + LLRect label_rect = mLabel->getRect(); // Button // Note: button cover the label by extending all the way to the right. - LLRect btn_rect; + LLRect btn_rect = p.check_button.rect(); btn_rect.setOriginAndSize( - llcheckboxctrl_hpad, - llcheckboxctrl_vpad, - llcheckboxctrl_btn_size + llcheckboxctrl_spacing + text_width + llcheckboxctrl_hpad, - llmax( text_height, llcheckboxctrl_btn_size() ) + llcheckboxctrl_vpad); + btn_rect.mLeft, + btn_rect.mBottom, + llmax(btn_rect.mRight, label_rect.mRight - btn_rect.mLeft), + llmax( label_rect.getHeight(), btn_rect.mTop)); std::string active_true_id, active_false_id; std::string inactive_true_id, inactive_false_id; @@ -174,31 +166,20 @@ void LLCheckBoxCtrl::clear() void LLCheckBoxCtrl::reshape(S32 width, S32 height, BOOL called_from_parent) { - //stretch or shrink bounding rectangle of label when rebuilding UI at new scale - static LLUICachedControl<S32> llcheckboxctrl_spacing ("UICheckboxctrlSpacing", 0); - static LLUICachedControl<S32> llcheckboxctrl_hpad ("UICheckboxctrlHPad", 0); - static LLUICachedControl<S32> llcheckboxctrl_vpad ("UICheckboxctrlVPad", 0); - static LLUICachedControl<S32> llcheckboxctrl_btn_size ("UICheckboxctrlBtnSize", 0); - S32 text_width = mLabel->getTextBoundingRect().getWidth(); - S32 text_height = llround(mFont->getLineHeight()); - LLRect label_rect; - label_rect.setOriginAndSize( - llcheckboxctrl_hpad + llcheckboxctrl_btn_size + llcheckboxctrl_spacing, - llcheckboxctrl_vpad, - text_width, - text_height ); - mLabel->setShape(label_rect); - - LLRect btn_rect; + mLabel->reshapeToFitText(); + + LLRect label_rect = mLabel->getRect(); + + // Button + // Note: button cover the label by extending all the way to the right. + LLRect btn_rect = mButton->getRect(); btn_rect.setOriginAndSize( - llcheckboxctrl_hpad, - llcheckboxctrl_vpad, - llcheckboxctrl_btn_size + llcheckboxctrl_spacing + text_width, - llmax( text_height, llcheckboxctrl_btn_size() ) ); - mButton->setShape( btn_rect ); - - LLUICtrl::reshape(width, height, called_from_parent); + btn_rect.mLeft, + btn_rect.mBottom, + llmax(btn_rect.getWidth(), label_rect.mRight - btn_rect.mLeft), + llmax(label_rect.mTop - btn_rect.mBottom, btn_rect.getHeight())); + mButton->setShape(btn_rect); } //virtual diff --git a/indra/llui/llclipboard.cpp b/indra/llui/llclipboard.cpp index 984c4ec5fb..14173fdbb0 100644 --- a/indra/llui/llclipboard.cpp +++ b/indra/llui/llclipboard.cpp @@ -34,103 +34,113 @@ #include "llview.h" #include "llwindow.h" -// Global singleton -LLClipboard gClipboard; - - -LLClipboard::LLClipboard() +LLClipboard::LLClipboard() : + mGeneration(0) { + reset(); } - LLClipboard::~LLClipboard() { + reset(); } - -void LLClipboard::copyFromSubstring(const LLWString &src, S32 pos, S32 len, const LLUUID& source_id ) +void LLClipboard::reset() { - mSourceID = source_id; - mString = src.substr(pos, len); - LLView::getWindow()->copyTextToClipboard( mString ); + // Increment the clipboard count + mGeneration++; + // Clear the clipboard + mObjects.clear(); + mCutMode = false; + mString = LLWString(); } -void LLClipboard::copyFromString(const LLWString &src, const LLUUID& source_id ) +// Copy the input uuid to the LL clipboard +bool LLClipboard::copyToClipboard(const LLUUID& src, const LLAssetType::EType type) { - mSourceID = source_id; - mString = src; - LLView::getWindow()->copyTextToClipboard( mString ); + reset(); + return addToClipboard(src, type); } -const LLWString& LLClipboard::getPasteWString( LLUUID* source_id ) +// Add the input uuid to the LL clipboard +// Convert the uuid to string and concatenate that string to the system clipboard if legit +bool LLClipboard::addToClipboard(const LLUUID& src, const LLAssetType::EType type) { - if( mSourceID.notNull() ) + bool res = false; + if (src.notNull()) { - LLWString temp_string; - LLView::getWindow()->pasteTextFromClipboard(temp_string); - - if( temp_string != mString ) + res = true; + if (LLAssetType::lookupIsAssetIDKnowable(type)) { - mSourceID.setNull(); - mString = temp_string; + LLWString source = utf8str_to_wstring(src.asString()); + res = addToClipboard(source, 0, source.size()); + } + if (res) + { + mObjects.push_back(src); + mGeneration++; } } - else - { - LLView::getWindow()->pasteTextFromClipboard(mString); - } + return res; +} - if( source_id ) +bool LLClipboard::pasteFromClipboard(std::vector<LLUUID>& inv_objects) const +{ + bool res = false; + S32 count = mObjects.size(); + if (count > 0) { - *source_id = mSourceID; + res = true; + inv_objects.clear(); + for (S32 i = 0; i < count; i++) + { + inv_objects.push_back(mObjects[i]); + } } - - return mString; + return res; } +// Returns true if the LL Clipboard has pasteable items in it +bool LLClipboard::hasContents() const +{ + return (mObjects.size() > 0); +} -BOOL LLClipboard::canPasteString() const +// Returns true if the input uuid is in the list of clipboard objects +bool LLClipboard::isOnClipboard(const LLUUID& object) const { - return LLView::getWindow()->isClipboardTextAvailable(); + std::vector<LLUUID>::const_iterator iter = std::find(mObjects.begin(), mObjects.end(), object); + return (iter != mObjects.end()); } +// Copy the input string to the LL and the system clipboard +bool LLClipboard::copyToClipboard(const LLWString &src, S32 pos, S32 len, bool use_primary) +{ + return addToClipboard(src, pos, len, use_primary); +} -void LLClipboard::copyFromPrimarySubstring(const LLWString &src, S32 pos, S32 len, const LLUUID& source_id ) +// Concatenate the input string to the LL and the system clipboard +bool LLClipboard::addToClipboard(const LLWString &src, S32 pos, S32 len, bool use_primary) { - mSourceID = source_id; mString = src.substr(pos, len); - LLView::getWindow()->copyTextToPrimary( mString ); + return (use_primary ? LLView::getWindow()->copyTextToPrimary(mString) : LLView::getWindow()->copyTextToClipboard(mString)); } - -const LLWString& LLClipboard::getPastePrimaryWString( LLUUID* source_id ) +// Copy the System clipboard to the output string. +// Manage the LL Clipboard / System clipboard consistency +bool LLClipboard::pasteFromClipboard(LLWString &dst, bool use_primary) { - if( mSourceID.notNull() ) + bool res = (use_primary ? LLView::getWindow()->pasteTextFromPrimary(dst) : LLView::getWindow()->pasteTextFromClipboard(dst)); + if (res) { - LLWString temp_string; - LLView::getWindow()->pasteTextFromPrimary(temp_string); - - if( temp_string != mString ) - { - mSourceID.setNull(); - mString = temp_string; - } + mString = dst; } - else - { - LLView::getWindow()->pasteTextFromPrimary(mString); - } - - if( source_id ) - { - *source_id = mSourceID; - } - - return mString; + return res; } - -BOOL LLClipboard::canPastePrimaryString() const +// Return true if there's something on the System clipboard +bool LLClipboard::isTextAvailable(bool use_primary) const { - return LLView::getWindow()->isPrimaryTextAvailable(); + return (use_primary ? LLView::getWindow()->isPrimaryTextAvailable() : LLView::getWindow()->isClipboardTextAvailable()); } + diff --git a/indra/llui/llclipboard.h b/indra/llui/llclipboard.h index 24cb46c3f4..fd2e7610df 100644 --- a/indra/llui/llclipboard.h +++ b/indra/llui/llclipboard.h @@ -27,39 +27,68 @@ #ifndef LL_LLCLIPBOARD_H #define LL_LLCLIPBOARD_H +#include <boost/function.hpp> #include "llstring.h" #include "lluuid.h" +#include "stdenums.h" +#include "llsingleton.h" +#include "llassettype.h" +#include "llinventory.h" +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLClipboard +// +// This class is used to cut/copy/paste text strings and inventory items around +// the world. Use LLClipboard::instance().method() to use its methods. +// Note that the text and UUIDs are loosely coupled only. There are few cases +// where the viewer does offer a serialized version of the UUID on the clipboard. +// In those case, the text is overridden when copying/cutting the item. +// In all other cases, the text and the UUIDs are very much independent. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLClipboard +class LLClipboard : public LLSingleton<LLClipboard> { public: LLClipboard(); ~LLClipboard(); + + // Clears the clipboard + void reset(); + // Returns the state of the clipboard so client can know if it has been modified (comparing with tracked state) + int getGeneration() const { return mGeneration; } - /* We support two flavors of clipboard. The default is the explicitly - copy-and-pasted clipboard. The second is the so-called 'primary' clipboard - which is implicitly copied upon selection on platforms which expect this - (i.e. X11/Linux). */ + // Text strings management: + // ------------------------ + // We support two flavors of text clipboards. The default is the explicitly + // copy-and-pasted clipboard. The second is the so-called 'primary' clipboard + // which is implicitly copied upon selection on platforms which expect this + // (i.e. X11/Linux, Mac). + bool copyToClipboard(const LLWString& src, S32 pos, S32 len, bool use_primary = false); + bool addToClipboard(const LLWString& src, S32 pos, S32 len, bool use_primary = false); + bool pasteFromClipboard(LLWString& dst, bool use_primary = false); + bool isTextAvailable(bool use_primary = false) const; + + // Object list management: + // ----------------------- + // Clears and adds one single object to the clipboard + bool copyToClipboard(const LLUUID& src, const LLAssetType::EType type = LLAssetType::AT_NONE); + // Adds one object to the current list of objects on the clipboard + bool addToClipboard(const LLUUID& src, const LLAssetType::EType type = LLAssetType::AT_NONE); + // Gets a copy of the objects on the clipboard + bool pasteFromClipboard(std::vector<LLUUID>& inventory_objects) const; + + bool hasContents() const; // True if the clipboard has pasteable objects + bool isOnClipboard(const LLUUID& object) const; // True if the input object uuid is on the clipboard - void copyFromSubstring(const LLWString ©_from, S32 pos, S32 len, const LLUUID& source_id = LLUUID::null ); - void copyFromString(const LLWString ©_from, const LLUUID& source_id = LLUUID::null ); - BOOL canPasteString() const; - const LLWString& getPasteWString(LLUUID* source_id = NULL); - - void copyFromPrimarySubstring(const LLWString ©_from, S32 pos, S32 len, const LLUUID& source_id = LLUUID::null ); - BOOL canPastePrimaryString() const; - const LLWString& getPastePrimaryWString(LLUUID* source_id = NULL); + bool isCutMode() const { return mCutMode; } + void setCutMode(bool mode) { mCutMode = mode; mGeneration++; } private: - LLUUID mSourceID; - LLWString mString; + std::vector<LLUUID> mObjects; // Objects on the clipboard. Can be empty while mString contains something licit (e.g. text from chat) + LLWString mString; // The text string. If mObjects is not empty, this string is reflecting them (UUIDs for the moment) if the asset type is knowable. + bool mCutMode; // This is a convenience flag for the viewer. + int mGeneration; // Incremented when the clipboard changes so that interested parties can check for changes on the clipboard. }; - -// Global singleton -extern LLClipboard gClipboard; - - #endif // LL_LLCLIPBOARD_H diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 2dabbc7767..806d2ef3f6 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -94,6 +94,7 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p) mMaxChars(p.max_chars), mPrearrangeCallback(p.prearrange_callback()), mTextEntryCallback(p.text_entry_callback()), + mTextChangedCallback(p.text_changed_callback()), mListPosition(p.list_position), mLastSelectedIndex(-1), mLabel(p.label) @@ -230,6 +231,10 @@ void LLComboBox::resetDirty() } } +bool LLComboBox::itemExists(const std::string& name) +{ + return mList->getItemByLabel(name); +} // add item "name" to menu LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, BOOL enabled) @@ -315,7 +320,7 @@ void LLComboBox::setValue(const LLSD& value) LLScrollListItem* item = mList->getFirstSelected(); if (item) { - setLabel(getSelectedItemLabel()); + updateLabel(); } mLastSelectedIndex = mList->getFirstSelectedIndex(); } @@ -383,6 +388,23 @@ void LLComboBox::setLabel(const LLStringExplicit& name) } } +void LLComboBox::updateLabel() +{ + // Update the combo editor with the selected + // item label. + if (mTextEntry) + { + mTextEntry->setText(getSelectedItemLabel()); + mTextEntry->setTentative(FALSE); + } + + // If combo box doesn't allow text entry update + // the combo button label. + if (!mAllowTextEntry) + { + mButton->setLabel(getSelectedItemLabel()); + } +} BOOL LLComboBox::remove(const std::string& name) { @@ -503,15 +525,12 @@ void LLComboBox::createLineEditor(const LLComboBox::Params& p) else { mButton->setRect(rect); - mButton->setTabStop(TRUE); - mButton->setHAlign(LLFontGL::LEFT); mButton->setLabel(mLabel.getString()); if (mTextEntry) { mTextEntry->setVisible(FALSE); } - mButton->setFollowsAll(); } } @@ -594,7 +613,7 @@ void LLComboBox::showList() } mList->setOrigin(rect.mLeft, rect.mBottom); mList->reshape(rect.getWidth(), rect.getHeight()); - mList->translateIntoRect(root_view_local, FALSE); + mList->translateIntoRect(root_view_local); // Make sure we didn't go off bottom of screen S32 x, y; @@ -700,13 +719,13 @@ void LLComboBox::onItemSelected(const LLSD& data) mLastSelectedIndex = getCurrentIndex(); if (mLastSelectedIndex != -1) { - setLabel(getSelectedItemLabel()); + updateLabel(); if (mAllowTextEntry) - { - gFocusMgr.setKeyboardFocus(mTextEntry); - mTextEntry->selectAll(); - } + { + gFocusMgr.setKeyboardFocus(mTextEntry); + mTextEntry->selectAll(); + } } // hiding the list reasserts the old value stored in the text editor/dropdown button hideList(); @@ -769,7 +788,10 @@ BOOL LLComboBox::handleKeyHere(KEY key, MASK mask) return FALSE; } // if selection has changed, pop open list - else if (mList->getLastSelectedItem() != last_selected_item) + else if (mList->getLastSelectedItem() != last_selected_item + || ((key == KEY_DOWN || key == KEY_UP) + && mList->getCanSelect() + && !mList->isEmpty())) { showList(); } @@ -833,6 +855,10 @@ void LLComboBox::onTextEntry(LLLineEditor* line_editor) mList->deselectAllItems(); mLastSelectedIndex = -1; } + if (mTextChangedCallback != NULL) + { + (mTextChangedCallback)(line_editor, LLSD()); + } return; } @@ -877,6 +903,10 @@ void LLComboBox::onTextEntry(LLLineEditor* line_editor) // RN: presumably text entry updateSelection(); } + if (mTextChangedCallback != NULL) + { + (mTextChangedCallback)(line_editor, LLSD()); + } } void LLComboBox::updateSelection() diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index 5f0e4a6843..64dbaea306 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -73,7 +73,8 @@ public: allow_new_values; Optional<S32> max_chars; Optional<commit_callback_t> prearrange_callback, - text_entry_callback; + text_entry_callback, + text_changed_callback; Optional<EPreferredPosition, PreferredPositionValues> list_position; @@ -133,6 +134,7 @@ public: LLScrollListItem* addSeparator(EAddPosition pos = ADD_BOTTOM); BOOL remove( S32 index ); // remove item by index, return TRUE if found and removed void removeall() { clearRows(); } + bool itemExists(const std::string& name); void sortByName(BOOL ascending = TRUE); // Sort the entries in the combobox by name @@ -147,6 +149,9 @@ public: // This is probably a UI abuse. void setLabel(const LLStringExplicit& name); + // Updates the combobox label to match the selected list item. + void updateLabel(); + BOOL remove(const std::string& name); // remove item "name", return TRUE if found and removed BOOL setCurrentByIndex( S32 index ); @@ -190,6 +195,7 @@ public: void setPrearrangeCallback( commit_callback_t cb ) { mPrearrangeCallback = cb; } void setTextEntryCallback( commit_callback_t cb ) { mTextEntryCallback = cb; } + void setTextChangedCallback( commit_callback_t cb ) { mTextChangedCallback = cb; } void setButtonVisible(BOOL visible); @@ -220,6 +226,7 @@ private: BOOL mTextEntryTentative; commit_callback_t mPrearrangeCallback; commit_callback_t mTextEntryCallback; + commit_callback_t mTextChangedCallback; commit_callback_t mSelectionCallback; boost::signals2::connection mTopLostSignalConnection; S32 mLastSelectedIndex; diff --git a/indra/llui/llcommandmanager.cpp b/indra/llui/llcommandmanager.cpp new file mode 100644 index 0000000000..0e2f3f1961 --- /dev/null +++ b/indra/llui/llcommandmanager.cpp @@ -0,0 +1,172 @@ +/** + * @file llcommandmanager.cpp + * @brief LLCommandManager class + * + * $LicenseInfo:firstyear=2001&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$ + */ + +// A control that displays the name of the chosen item, which when +// clicked shows a scrolling box of options. + +#include "linden_common.h" + +#include "llcommandmanager.h" +#include "lldir.h" +#include "llerror.h" +#include "llxuiparser.h" + +#include <boost/foreach.hpp> + + +// +// LLCommandId class +// + +const LLCommandId LLCommandId::null = LLCommandId("null command"); + +// +// LLCommand class +// + +LLCommand::Params::Params() + : available_in_toybox("available_in_toybox", false) + , icon("icon") + , label_ref("label_ref") + , name("name") + , tooltip_ref("tooltip_ref") + , execute_function("execute_function") + , execute_parameters("execute_parameters") + , execute_stop_function("execute_stop_function") + , execute_stop_parameters("execute_stop_parameters") + , is_enabled_function("is_enabled_function") + , is_enabled_parameters("is_enabled_parameters") + , is_running_function("is_running_function") + , is_running_parameters("is_running_parameters") + , is_starting_function("is_starting_function") + , is_starting_parameters("is_starting_parameters") +{ +} + +LLCommand::LLCommand(const LLCommand::Params& p) + : mIdentifier(p.name) + , mAvailableInToybox(p.available_in_toybox) + , mIcon(p.icon) + , mLabelRef(p.label_ref) + , mName(p.name) + , mTooltipRef(p.tooltip_ref) + , mExecuteFunction(p.execute_function) + , mExecuteParameters(p.execute_parameters) + , mExecuteStopFunction(p.execute_stop_function) + , mExecuteStopParameters(p.execute_stop_parameters) + , mIsEnabledFunction(p.is_enabled_function) + , mIsEnabledParameters(p.is_enabled_parameters) + , mIsRunningFunction(p.is_running_function) + , mIsRunningParameters(p.is_running_parameters) + , mIsStartingFunction(p.is_starting_function) + , mIsStartingParameters(p.is_starting_parameters) +{ +} + + +// +// LLCommandManager class +// + +LLCommandManager::LLCommandManager() +{ +} + +LLCommandManager::~LLCommandManager() +{ + for (CommandVector::iterator cmdIt = mCommands.begin(); cmdIt != mCommands.end(); ++cmdIt) + { + LLCommand * command = *cmdIt; + + delete command; + } +} + +U32 LLCommandManager::commandCount() const +{ + return mCommands.size(); +} + +LLCommand * LLCommandManager::getCommand(U32 commandIndex) +{ + return mCommands[commandIndex]; +} + +LLCommand * LLCommandManager::getCommand(const LLCommandId& commandId) +{ + LLCommand * command_match = NULL; + + CommandIndexMap::const_iterator found = mCommandIndices.find(commandId.uuid()); + + if (found != mCommandIndices.end()) + { + command_match = mCommands[found->second]; + } + + return command_match; +} + +void LLCommandManager::addCommand(LLCommand * command) +{ + LLCommandId command_id = command->id(); + mCommandIndices[command_id.uuid()] = mCommands.size(); + mCommands.push_back(command); + + lldebugs << "Successfully added command: " << command->name() << llendl; +} + +//static +bool LLCommandManager::load() +{ + LLCommandManager& mgr = LLCommandManager::instance(); + + std::string commands_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "commands.xml"); + + LLCommandManager::Params commandsParams; + + LLSimpleXUIParser parser; + + if (!parser.readXUI(commands_file, commandsParams)) + { + llerrs << "Unable to load xml file: " << commands_file << llendl; + return false; + } + + if (!commandsParams.validateBlock()) + { + llerrs << "Invalid commands file: " << commands_file << llendl; + return false; + } + + BOOST_FOREACH(LLCommand::Params& commandParams, commandsParams.commands) + { + LLCommand * command = new LLCommand(commandParams); + + mgr.addCommand(command); + } + + return true; +} diff --git a/indra/llui/llcommandmanager.h b/indra/llui/llcommandmanager.h new file mode 100644 index 0000000000..a7276a48aa --- /dev/null +++ b/indra/llui/llcommandmanager.h @@ -0,0 +1,202 @@ +/** + * @file llcommandmanager.h + * @brief LLCommandManager class to hold commands + * + * $LicenseInfo:firstyear=2001&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_LLCOMMANDMANAGER_H +#define LL_LLCOMMANDMANAGER_H + +#include "llinitparam.h" +#include "llsingleton.h" + + +class LLCommand; +class LLCommandManager; + + +class LLCommandId +{ +public: + friend class LLCommand; + friend class LLCommandManager; + + struct Params : public LLInitParam::Block<Params> + { + Mandatory<std::string> name; + + Params() + : name("name") + {} + }; + + LLCommandId(const std::string& name) + { + mUUID = LLUUID::generateNewID(name); + } + + LLCommandId(const Params& p) + { + mUUID = LLUUID::generateNewID(p.name); + } + + LLCommandId(const LLUUID& uuid) + : mUUID(uuid) + {} + + const LLUUID& uuid() const { return mUUID; } + + bool operator!=(const LLCommandId& command) const + { + return (mUUID != command.mUUID); + } + + bool operator==(const LLCommandId& command) const + { + return (mUUID == command.mUUID); + } + + static const LLCommandId null; + +private: + LLUUID mUUID; +}; + +typedef std::list<LLCommandId> command_id_list_t; + + +class LLCommand +{ +public: + struct Params : public LLInitParam::Block<Params> + { + Mandatory<bool> available_in_toybox; + Mandatory<std::string> icon; + Mandatory<std::string> label_ref; + Mandatory<std::string> name; + Mandatory<std::string> tooltip_ref; + + Mandatory<std::string> execute_function; + Optional<LLSD> execute_parameters; + + Optional<std::string> execute_stop_function; + Optional<LLSD> execute_stop_parameters; + + Optional<std::string> is_enabled_function; + Optional<LLSD> is_enabled_parameters; + + Optional<std::string> is_running_function; + Optional<LLSD> is_running_parameters; + + Optional<std::string> is_starting_function; + Optional<LLSD> is_starting_parameters; + + Params(); + }; + + LLCommand(const LLCommand::Params& p); + + const bool availableInToybox() const { return mAvailableInToybox; } + const std::string& icon() const { return mIcon; } + const LLCommandId& id() const { return mIdentifier; } + const std::string& labelRef() const { return mLabelRef; } + const std::string& name() const { return mName; } + const std::string& tooltipRef() const { return mTooltipRef; } + + const std::string& executeFunctionName() const { return mExecuteFunction; } + const LLSD& executeParameters() const { return mExecuteParameters; } + + const std::string& executeStopFunctionName() const { return mExecuteStopFunction; } + const LLSD& executeStopParameters() const { return mExecuteStopParameters; } + + const std::string& isEnabledFunctionName() const { return mIsEnabledFunction; } + const LLSD& isEnabledParameters() const { return mIsEnabledParameters; } + + const std::string& isRunningFunctionName() const { return mIsRunningFunction; } + const LLSD& isRunningParameters() const { return mIsRunningParameters; } + + const std::string& isStartingFunctionName() const { return mIsStartingFunction; } + const LLSD& isStartingParameters() const { return mIsStartingParameters; } + +private: + LLCommandId mIdentifier; + + bool mAvailableInToybox; + std::string mIcon; + std::string mLabelRef; + std::string mName; + std::string mTooltipRef; + + std::string mExecuteFunction; + LLSD mExecuteParameters; + + std::string mExecuteStopFunction; + LLSD mExecuteStopParameters; + + std::string mIsEnabledFunction; + LLSD mIsEnabledParameters; + + std::string mIsRunningFunction; + LLSD mIsRunningParameters; + + std::string mIsStartingFunction; + LLSD mIsStartingParameters; +}; + + +class LLCommandManager +: public LLSingleton<LLCommandManager> +{ +public: + struct Params : public LLInitParam::Block<Params> + { + Multiple< LLCommand::Params, AtLeast<1> > commands; + + Params() + : commands("command") + { + } + }; + + LLCommandManager(); + ~LLCommandManager(); + + U32 commandCount() const; + LLCommand * getCommand(U32 commandIndex); + LLCommand * getCommand(const LLCommandId& commandId); + + static bool load(); + +protected: + void addCommand(LLCommand * command); + +private: + typedef std::map<LLUUID, U32> CommandIndexMap; + typedef std::vector<LLCommand *> CommandVector; + + CommandVector mCommands; + CommandIndexMap mCommandIndices; +}; + + +#endif // LL_LLCOMMANDMANAGER_H diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp index 06bad1f371..161496b1f5 100644 --- a/indra/llui/llconsole.cpp +++ b/indra/llui/llconsole.cpp @@ -132,6 +132,9 @@ void LLConsole::setFontSize(S32 size_index) void LLConsole::draw() { + // Units in pixels + static const F32 padding_horizontal = 10; + static const F32 padding_vertical = 3; LLGLSUIDefault gls_ui; // skip lines added more than mLinePersistTime ago @@ -176,11 +179,9 @@ void LLConsole::draw() // draw remaining lines F32 y_pos = 0.f; - LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); + LLUIImagePtr imagep = LLUI::getUIImage("transparent"); -// F32 console_opacity = llclamp(gSavedSettings.getF32("ConsoleBackgroundOpacity"), 0.f, 1.f); F32 console_opacity = llclamp(LLUI::sSettingGroups["config"]->getF32("ConsoleBackgroundOpacity"), 0.f, 1.f); -// LLColor4 color = LLUIColorTable::instance().getColor("ConsoleBackground"); LLColor4 color = LLUIColorTable::instance().getColor("ConsoleBackground"); color.mV[VALPHA] *= console_opacity; @@ -188,8 +189,8 @@ void LLConsole::draw() for(paragraph_it = mParagraphs.rbegin(); paragraph_it != mParagraphs.rend(); paragraph_it++) { - S32 target_height = llfloor( (*paragraph_it).mLines.size() * line_height + 8); - S32 target_width = llfloor( (*paragraph_it).mMaxWidth +15); + S32 target_height = llfloor( (*paragraph_it).mLines.size() * line_height + padding_vertical); + S32 target_width = llfloor( (*paragraph_it).mMaxWidth + padding_horizontal); y_pos += ((*paragraph_it).mLines.size()) * line_height; imagep->drawSolid(-14, (S32)(y_pos + line_height - target_height), target_width, target_height, color); @@ -234,7 +235,7 @@ void LLConsole::draw() y_off += line_height; } } - y_pos += 8; + y_pos += padding_vertical; } } @@ -371,9 +372,7 @@ LLConsole::Paragraph::Paragraph (LLWString str, const LLColor4 &color, F32 add_t // static void LLConsole::updateClass() { - LLInstanceTrackerScopedGuard guard; - - for (instance_iter it = guard.beginInstances(); it != guard.endInstances(); ++it) + for (instance_iter it = beginInstances(); it != endInstances(); ++it) { it->update(); } diff --git a/indra/llui/llconsole.h b/indra/llui/llconsole.h index bb8ea50bed..f32f1dd74c 100644 --- a/indra/llui/llconsole.h +++ b/indra/llui/llconsole.h @@ -54,7 +54,7 @@ public: persist_time("persist_time", 0.f), // forever font_size_index("font_size_index") { - mouse_opaque(false); + changeDefault(mouse_opaque, false); } }; protected: diff --git a/indra/llui/llcontainerview.h b/indra/llui/llcontainerview.h index 7d3d5cf787..e81600fd6c 100644 --- a/indra/llui/llcontainerview.h +++ b/indra/llui/llcontainerview.h @@ -50,7 +50,7 @@ public: show_label("show_label", FALSE), display_children("display_children", TRUE) { - mouse_opaque(false); + changeDefault(mouse_opaque, false); } }; diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp index ca2dc644a4..3396213f1c 100644 --- a/indra/llui/lldockablefloater.cpp +++ b/indra/llui/lldockablefloater.cpp @@ -82,7 +82,7 @@ BOOL LLDockableFloater::postBuild() mForceDocking = true; } - mDockTongue = LLUI::getUIImage("windows/Flyout_Pointer.png"); + mDockTongue = LLUI::getUIImage("Flyout_Pointer"); LLFloater::setDocked(true); return LLView::postBuild(); } @@ -162,10 +162,15 @@ void LLDockableFloater::setVisible(BOOL visible) void LLDockableFloater::setMinimized(BOOL minimize) { - if(minimize) + if(minimize && isDocked()) { + // minimizing a docked floater just hides it setVisible(FALSE); } + else + { + LLFloater::setMinimized(minimize); + } } LLView * LLDockableFloater::getDockWidget() @@ -234,8 +239,21 @@ void LLDockableFloater::setDockControl(LLDockControl* dockControl) setDocked(isDocked()); } -const LLUIImagePtr& LLDockableFloater::getDockTongue() +const LLUIImagePtr& LLDockableFloater::getDockTongue(LLDockControl::DocAt dock_side) { + switch(dock_side) + { + case LLDockControl::LEFT: + mDockTongue = LLUI::getUIImage("Flyout_Left"); + break; + case LLDockControl::RIGHT: + mDockTongue = LLUI::getUIImage("Flyout_Right"); + break; + default: + mDockTongue = LLUI::getUIImage("Flyout_Pointer"); + break; + } + return mDockTongue; } diff --git a/indra/llui/lldockablefloater.h b/indra/llui/lldockablefloater.h index 8deb6c1159..89c9852f4a 100644 --- a/indra/llui/lldockablefloater.h +++ b/indra/llui/lldockablefloater.h @@ -113,6 +113,8 @@ public: bool getUniqueDocking() { return mUniqueDocking; } bool getUseTongue() { return mUseTongue; } + + void setUseTongue(bool use_tongue) { mUseTongue = use_tongue;} private: /** * Provides unique of dockable floater. @@ -122,7 +124,7 @@ private: protected: void setDockControl(LLDockControl* dockControl); - const LLUIImagePtr& getDockTongue(); + const LLUIImagePtr& getDockTongue(LLDockControl::DocAt dock_side = LLDockControl::TOP); // Checks if docking should be forced. // It may be useful e.g. if floater created in mouselook mode (see EXT-5609) diff --git a/indra/llui/lldockcontrol.cpp b/indra/llui/lldockcontrol.cpp index d48674f306..af39e41fa6 100644 --- a/indra/llui/lldockcontrol.cpp +++ b/indra/llui/lldockcontrol.cpp @@ -92,19 +92,24 @@ void LLDockControl::setDock(LLView* dockWidget) void LLDockControl::getAllowedRect(LLRect& rect) { - rect = mDockableFloater->getRootView()->getRect(); + rect = mDockableFloater->getRootView()->getChild<LLView>("non_toolbar_panel")->getRect(); } void LLDockControl::repositionDockable() { + if (!mDockWidget) return; LLRect dockRect = mDockWidget->calcScreenRect(); LLRect rootRect; + LLRect floater_rect = mDockableFloater->calcScreenRect(); mGetAllowedRectCallback(rootRect); - // recalculate dockable position if dock position changed, dock visibility changed, - // root view rect changed or recalculation is forced - if (mPrevDockRect != dockRect || mDockWidgetVisible != isDockVisible() - || mRootRect != rootRect || mRecalculateDocablePosition) + // recalculate dockable position if: + if (mPrevDockRect != dockRect //dock position changed + || mDockWidgetVisible != isDockVisible() //dock visibility changed + || mRootRect != rootRect //root view rect changed + || mFloaterRect != floater_rect //floater rect changed + || mRecalculateDockablePosition //recalculation is forced + ) { // undock dockable and off() if dock not visible if (!isDockVisible()) @@ -135,7 +140,8 @@ void LLDockControl::repositionDockable() mPrevDockRect = dockRect; mRootRect = rootRect; - mRecalculateDocablePosition = false; + mFloaterRect = floater_rect; + mRecalculateDockablePosition = false; mDockWidgetVisible = isDockVisible(); } } @@ -160,8 +166,10 @@ bool LLDockControl::isDockVisible() case TOP: { // check is dock inside parent rect + // assume that parent for all dockable floaters + // is the root view LLRect dockParentRect = - mDockWidget->getParent()->calcScreenRect(); + mDockWidget->getRootView()->calcScreenRect(); if (dockRect.mRight <= dockParentRect.mLeft || dockRect.mLeft >= dockParentRect.mRight) { @@ -200,30 +208,47 @@ void LLDockControl::moveDockable() switch (mDockAt) { case LEFT: - x = dockRect.mLeft; - y = dockRect.mTop + mDockTongue->getHeight() + dockableRect.getHeight(); - // check is dockable inside root view rect - if (x < rootRect.mLeft) + + x = dockRect.mLeft - dockableRect.getWidth(); + y = dockRect.getCenterY() + dockableRect.getHeight() / 2; + + if (use_tongue) { - x = rootRect.mLeft; + x -= mDockTongue->getWidth(); } - if (x + dockableRect.getWidth() > rootRect.mRight) + + mDockTongueX = dockableRect.mRight; + mDockTongueY = dockableRect.getCenterY() - mDockTongue->getHeight() / 2; + + break; + + case RIGHT: + + x = dockRect.mRight; + y = dockRect.getCenterY() + dockableRect.getHeight() / 2; + + if (use_tongue) { - x = rootRect.mRight - dockableRect.getWidth(); + x += mDockTongue->getWidth(); } - - mDockTongueX = x + dockableRect.getWidth()/2 - mDockTongue->getWidth() / 2; - - mDockTongueY = dockRect.mTop; + + mDockTongueX = dockRect.mRight; + mDockTongueY = dockableRect.getCenterY() - mDockTongue->getHeight() / 2; + break; case TOP: x = dockRect.getCenterX() - dockableRect.getWidth() / 2; y = dockRect.mTop + dockableRect.getHeight(); - // unique docking used with dock tongue, so add tongue height o the Y coordinate + // unique docking used with dock tongue, so add tongue height to the Y coordinate if (use_tongue) { y += mDockTongue->getHeight(); + + if ( y > rootRect.mTop) + { + y = rootRect.mTop; + } } // check is dockable inside root view rect @@ -257,7 +282,7 @@ void LLDockControl::moveDockable() case BOTTOM: x = dockRect.getCenterX() - dockableRect.getWidth() / 2; y = dockRect.mBottom; - // unique docking used with dock tongue, so add tongue height o the Y coordinate + // unique docking used with dock tongue, so add tongue height to the Y coordinate if (use_tongue) { y -= mDockTongue->getHeight(); @@ -292,16 +317,27 @@ void LLDockControl::moveDockable() break; } - // move dockable - dockableRect.setLeftTopAndSize(x, y, dockableRect.getWidth(), - dockableRect.getHeight()); + S32 max_available_height = rootRect.getHeight() - (rootRect.mBottom - mDockTongueY) - mDockTongue->getHeight(); + + // A floater should be shrunk so it doesn't cover a part of its docking tongue and + // there is a space between a dockable floater and a control to which it is docked. + if (use_tongue && dockableRect.getHeight() >= max_available_height) + { + dockableRect.setLeftTopAndSize(x, y, dockableRect.getWidth(), max_available_height); + mDockableFloater->reshape(dockableRect.getWidth(), dockableRect.getHeight()); + } + else + { + // move dockable + dockableRect.setLeftTopAndSize(x, y, dockableRect.getWidth(), + dockableRect.getHeight()); + } + LLRect localDocableParentRect; - mDockableFloater->getParent()->screenRectToLocal(dockableRect, - &localDocableParentRect); - mDockableFloater->setRect(localDocableParentRect); - mDockableFloater->screenPointToLocal(mDockTongueX, mDockTongueY, - &mDockTongueX, &mDockTongueY); + mDockableFloater->getParent()->screenRectToLocal(dockableRect, &localDocableParentRect); + mDockableFloater->setRect(localDocableParentRect); + mDockableFloater->screenPointToLocal(mDockTongueX, mDockTongueY, &mDockTongueX, &mDockTongueY); } @@ -310,7 +346,7 @@ void LLDockControl::on() if (isDockVisible()) { mEnabled = true; - mRecalculateDocablePosition = true; + mRecalculateDockablePosition = true; } } @@ -321,7 +357,7 @@ void LLDockControl::off() void LLDockControl::forceRecalculatePosition() { - mRecalculateDocablePosition = true; + mRecalculateDockablePosition = true; } void LLDockControl::drawToungue() diff --git a/indra/llui/lldockcontrol.h b/indra/llui/lldockcontrol.h index 2e7359245f..c9602011f6 100644 --- a/indra/llui/lldockcontrol.h +++ b/indra/llui/lldockcontrol.h @@ -43,6 +43,7 @@ public: { TOP, LEFT, + RIGHT, BOTTOM }; @@ -79,12 +80,13 @@ private: private: get_allowed_rect_callback_t mGetAllowedRectCallback; bool mEnabled; - bool mRecalculateDocablePosition; + bool mRecalculateDockablePosition; bool mDockWidgetVisible; DocAt mDockAt; LLView* mDockWidget; LLRect mPrevDockRect; LLRect mRootRect; + LLRect mFloaterRect; LLFloater* mDockableFloater; LLUIImagePtr mDockTongue; S32 mDockTongueX; diff --git a/indra/llui/lldraghandle.cpp b/indra/llui/lldraghandle.cpp index 42e6c3c786..5f69c6af31 100644 --- a/indra/llui/lldraghandle.cpp +++ b/indra/llui/lldraghandle.cpp @@ -244,7 +244,7 @@ void LLDragHandleTop::reshapeTitleBox() const LLFontGL* font = LLFontGL::getFontSansSerif(); S32 title_width = getRect().getWidth(); title_width -= LEFT_PAD + 2 * BORDER_PAD + getButtonsRect().getWidth(); - S32 title_height = llround(font->getLineHeight()); + S32 title_height = font->getLineHeight(); LLRect title_rect; title_rect.setLeftTopAndSize( LEFT_PAD, diff --git a/indra/llui/lldraghandle.h b/indra/llui/lldraghandle.h index 7c56475e75..e095e577b1 100644 --- a/indra/llui/lldraghandle.h +++ b/indra/llui/lldraghandle.h @@ -51,8 +51,8 @@ public: drag_highlight_color("drag_highlight_color", LLUIColorTable::instance().getColor("DefaultHighlightLight")), drag_shadow_color("drag_shadow_color", LLUIColorTable::instance().getColor("DefaultShadowDark")) { - mouse_opaque(true); - follows.flags(FOLLOWS_ALL); + changeDefault(mouse_opaque, true); + changeDefault(follows.flags, FOLLOWS_ALL); } }; void initFromParams(const Params&); diff --git a/indra/llui/llfiltereditor.h b/indra/llui/llfiltereditor.h index 710699fdc1..3a05bc05a1 100644 --- a/indra/llui/llfiltereditor.h +++ b/indra/llui/llfiltereditor.h @@ -42,12 +42,7 @@ class LLFilterEditor : public LLSearchEditor { public: struct Params : public LLInitParam::Block<Params, LLSearchEditor::Params> - { - Params() - { - name = "filter_editor"; - } - }; + {}; protected: LLFilterEditor(const Params&); diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index c57c02f4b1..97a52fada4 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -87,9 +87,6 @@ bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null* mItemsPanel->addChild(item); break; default: - LL_WARNS("") << "Unsupported position." << LL_ENDL; - delete new_pair; - return false; break; } diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 0515853698..92bf429031 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -450,8 +450,9 @@ private: */ class LLFlatListViewEx : public LLFlatListView { - LOG_CLASS(LLFlatListViewEx); public: + LOG_CLASS(LLFlatListViewEx); + struct Params : public LLInitParam::Block<Params, LLFlatListView::Params> { /** diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 34d8e9c500..8ca1e685a9 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -1,4 +1,5 @@ /** + * @file llfloater.cpp * @brief LLFloater base class * @@ -57,13 +58,22 @@ #include "llhelp.h" #include "llmultifloater.h" #include "llsdutil.h" +#include <boost/foreach.hpp> + // use this to control "jumping" behavior when Ctrl-Tabbing const S32 TABBED_FLOATER_OFFSET = 0; -// static -F32 LLFloater::sActiveFloaterTransparency = 0.0f; -F32 LLFloater::sInactiveFloaterTransparency = 0.0f; +namespace LLInitParam +{ + void TypeValues<LLFloaterEnums::EOpenPositioning>::declareValues() + { + declare("relative", LLFloaterEnums::POSITIONING_RELATIVE); + declare("cascading", LLFloaterEnums::POSITIONING_CASCADING); + declare("centered", LLFloaterEnums::POSITIONING_CENTERED); + declare("specified", LLFloaterEnums::POSITIONING_SPECIFIED); + } +} std::string LLFloater::sButtonNames[BUTTON_COUNT] = { @@ -103,7 +113,6 @@ LLFloater::click_callback LLFloater::sButtonCallbacks[BUTTON_COUNT] = LLMultiFloater* LLFloater::sHostp = NULL; BOOL LLFloater::sQuitting = FALSE; // Flag to prevent storing visibility controls while quitting -LLFloater::handle_map_t LLFloater::sFloaterMap; LLFloaterView* gFloaterView = NULL; @@ -157,7 +166,7 @@ LLFloater::Params::Params() : title("title"), short_title("short_title"), single_instance("single_instance", false), - auto_tile("auto_tile", false), + reuse_instance("reuse_instance", false), can_resize("can_resize", false), can_minimize("can_minimize", true), can_close("can_close", true), @@ -167,7 +176,8 @@ LLFloater::Params::Params() save_rect("save_rect", false), save_visibility("save_visibility", false), can_dock("can_dock", false), - open_centered("open_centered", false), + show_title("show_title", true), + positioning("positioning", LLFloaterEnums::POSITIONING_RELATIVE), header_height("header_height", 0), legacy_header_height("legacy_header_height", 0), close_image("close_image"), @@ -183,9 +193,10 @@ LLFloater::Params::Params() dock_pressed_image("dock_pressed_image"), help_pressed_image("help_pressed_image"), open_callback("open_callback"), - close_callback("close_callback") + close_callback("close_callback"), + follows("follows") { - visible = false; + changeDefault(visible, false); } @@ -208,14 +219,14 @@ void LLFloater::initClass() if (ctrl) { ctrl->getSignal()->connect(boost::bind(&LLFloater::updateActiveFloaterTransparency)); - sActiveFloaterTransparency = LLUI::sSettingGroups["config"]->getF32("ActiveFloaterTransparency"); + updateActiveFloaterTransparency(); } ctrl = LLUI::sSettingGroups["config"]->getControl("InactiveFloaterTransparency").get(); if (ctrl) { ctrl->getSignal()->connect(boost::bind(&LLFloater::updateInactiveFloaterTransparency)); - sInactiveFloaterTransparency = LLUI::sSettingGroups["config"]->getF32("InactiveFloaterTransparency"); + updateInactiveFloaterTransparency(); } } @@ -225,17 +236,18 @@ static LLWidgetNameRegistry::StaticRegistrar sRegisterFloaterParams(&typeid(LLFl LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) : LLPanel(), // intentionally do not pass params here, see initFromParams - mDragHandle(NULL), + mDragHandle(NULL), mTitle(p.title), mShortTitle(p.short_title), mSingleInstance(p.single_instance), + mReuseInstance(p.reuse_instance.isProvided() ? p.reuse_instance : p.single_instance), // reuse single-instance floaters by default mKey(key), - mAutoTile(p.auto_tile), mCanTearOff(p.can_tear_off), mCanMinimize(p.can_minimize), mCanClose(p.can_close), mDragOnLeft(p.can_drag_on_left), mResizable(p.can_resize), + mPositioning(p.positioning), mMinWidth(p.min_width), mMinHeight(p.min_height), mHeaderHeight(p.header_height), @@ -254,7 +266,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) mMinimizeSignal(NULL) // mNotificationContext(NULL) { - mHandle.bind(this); + mPosition.setFloater(*this); // mNotificationContext = new LLFloaterNotificationContext(getHandle()); // Clicks stop here. @@ -275,9 +287,6 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) initFromParams(p); - // chrome floaters don't take focus at all - setFocusRoot(!getIsChrome()); - initFloater(p); } @@ -312,9 +321,6 @@ void LLFloater::initFloater(const Params& p) // Floaters are created in the invisible state setVisible(FALSE); - // add self to handle->floater map - sFloaterMap[mHandle] = this; - if (!getParent()) { gFloaterView->addChild(this); @@ -368,13 +374,13 @@ void LLFloater::layoutDragHandle() // static void LLFloater::updateActiveFloaterTransparency() { - sActiveFloaterTransparency = LLUI::sSettingGroups["config"]->getF32("ActiveFloaterTransparency"); + sActiveControlTransparency = LLUI::sSettingGroups["config"]->getF32("ActiveFloaterTransparency"); } // static void LLFloater::updateInactiveFloaterTransparency() { - sInactiveFloaterTransparency = LLUI::sSettingGroups["config"]->getF32("InactiveFloaterTransparency"); + sInactiveControlTransparency = LLUI::sSettingGroups["config"]->getF32("InactiveFloaterTransparency"); } void LLFloater::addResizeCtrls() @@ -465,15 +471,24 @@ void LLFloater::layoutResizeCtrls() mResizeHandle[3]->setRect(rect); } -void LLFloater::enableResizeCtrls(bool enable) +void LLFloater::enableResizeCtrls(bool enable, bool width, bool height) { + mResizeBar[LLResizeBar::LEFT]->setVisible(enable && width); + mResizeBar[LLResizeBar::LEFT]->setEnabled(enable && width); + + mResizeBar[LLResizeBar::TOP]->setVisible(enable && height); + mResizeBar[LLResizeBar::TOP]->setEnabled(enable && height); + + mResizeBar[LLResizeBar::RIGHT]->setVisible(enable && width); + mResizeBar[LLResizeBar::RIGHT]->setEnabled(enable && width); + + mResizeBar[LLResizeBar::BOTTOM]->setVisible(enable && height); + mResizeBar[LLResizeBar::BOTTOM]->setEnabled(enable && height); + for (S32 i = 0; i < 4; ++i) { - mResizeBar[i]->setVisible(enable); - mResizeBar[i]->setEnabled(enable); - - mResizeHandle[i]->setVisible(enable); - mResizeHandle[i]->setEnabled(enable); + mResizeHandle[i]->setVisible(enable && width && height); + mResizeHandle[i]->setEnabled(enable && width && height); } } @@ -512,8 +527,6 @@ LLFloater::~LLFloater() // correct, non-minimized positions. setMinimized( FALSE ); - sFloaterMap.erase(mHandle); - delete mDragHandle; for (S32 i = 0; i < 4; i++) { @@ -521,7 +534,6 @@ LLFloater::~LLFloater() delete mResizeHandle[i]; } - storeRectControl(); setVisible(false); // We're not visible if we're destroyed storeVisibilityControl(); storeDockStateControl(); @@ -531,10 +543,18 @@ LLFloater::~LLFloater() void LLFloater::storeRectControl() { - if( mRectControl.size() > 1 ) + if (!mRectControl.empty()) { getControlGroup()->setRect( mRectControl, getRect() ); } + if (!mPosXControl.empty() && mPositioning == LLFloaterEnums::POSITIONING_RELATIVE) + { + getControlGroup()->setF32( mPosXControl, mPosition.mX ); + } + if (!mPosYControl.empty() && mPositioning == LLFloaterEnums::POSITIONING_RELATIVE) + { + getControlGroup()->setF32( mPosYControl, mPosition.mY ); + } } void LLFloater::storeVisibilityControl() @@ -553,23 +573,6 @@ void LLFloater::storeDockStateControl() } } -LLRect LLFloater::getSavedRect() const -{ - LLRect rect; - - if (mRectControl.size() > 1) - { - rect = getControlGroup()->getRect(mRectControl); - } - - return rect; -} - -bool LLFloater::hasSavedRect() const -{ - return !getSavedRect().isEmpty(); -} - // static std::string LLFloater::getControlName(const std::string& name, const LLSD& key) { @@ -666,6 +669,12 @@ void LLFloater::openFloater(const LLSD& key) } else { + LLFloater* floater_to_stack = LLFloaterReg::getLastFloaterInGroup(mInstanceName); + if (!floater_to_stack) + { + floater_to_stack = LLFloaterReg::getLastFloaterCascading(); + } + applyControlsAndPosition(floater_to_stack); setMinimized(FALSE); setVisibleAndFrontmost(mAutoFocus); } @@ -758,12 +767,19 @@ void LLFloater::closeFloater(bool app_quitting) else { setVisible(FALSE); + if (!mReuseInstance) + { + destroy(); + } } } else { setVisible(FALSE); // hide before destroying (so handleVisibilityChange() gets called) - destroy(); + if (!mReuseInstance) + { + destroy(); + } } } } @@ -772,7 +788,6 @@ void LLFloater::closeFloater(bool app_quitting) void LLFloater::reshape(S32 width, S32 height, BOOL called_from_parent) { LLPanel::reshape(width, height, called_from_parent); - storeRectControl(); } void LLFloater::releaseFocus() @@ -829,43 +844,153 @@ LLMultiFloater* LLFloater::getHost() return (LLMultiFloater*)mHostHandle.get(); } -void LLFloater::applySavedVariables() +void LLFloater::applyControlsAndPosition(LLFloater* other) { - applyRectControl(); - applyDockState(); + if (!applyDockState()) + { + if (!applyRectControl()) + { + applyPositioning(other, true); + } + } } -void LLFloater::applyRectControl() +bool LLFloater::applyRectControl() { - // first, center on screen if requested - if (mOpenCentered) + bool saved_rect = false; + + LLRect screen_rect = calcScreenRect(); + mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); + + LLFloater* last_in_group = LLFloaterReg::getLastFloaterInGroup(mInstanceName); + if (last_in_group && last_in_group != this) { - center(); + // other floaters in our group, position ourselves relative to them and don't save the rect + mRectControl.clear(); + mPositioning = LLFloaterEnums::POSITIONING_CASCADE_GROUP; } - - // override center if we have saved rect control - if (mRectControl.size() > 1) + else { - const LLRect& rect = getControlGroup()->getRect(mRectControl); - if (rect.getWidth() > 0 && rect.getHeight() > 0) + bool rect_specified = false; + if (!mRectControl.empty()) { - translate( rect.mLeft - getRect().mLeft, rect.mBottom - getRect().mBottom); - if (mResizable) + // If we have a saved rect, use it + const LLRect& rect = getControlGroup()->getRect(mRectControl); + if (rect.notEmpty()) saved_rect = true; + if (saved_rect) { - reshape(llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); + setOrigin(rect.mLeft, rect.mBottom); + + if (mResizable) + { + reshape(llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); + } + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + LLRect screen_rect = calcScreenRect(); + mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); + rect_specified = true; } } + + LLControlVariablePtr x_control = getControlGroup()->getControl(mPosXControl); + LLControlVariablePtr y_control = getControlGroup()->getControl(mPosYControl); + if (x_control.notNull() + && y_control.notNull() + && !x_control->isDefault() + && !y_control->isDefault()) + { + mPosition.mX = x_control->getValue().asReal(); + mPosition.mY = y_control->getValue().asReal(); + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + applyRelativePosition(); + + saved_rect = true; + } + + // remember updated position + if (rect_specified) + { + storeRectControl(); + } } + + if (saved_rect) + { + // propagate any derived positioning data back to settings file + storeRectControl(); + } + + + return saved_rect; } -void LLFloater::applyDockState() +bool LLFloater::applyDockState() { + bool docked = false; + if (mDocStateControl.size() > 1) { - bool dockState = getControlGroup()->getBOOL(mDocStateControl); - setDocked(dockState); + docked = getControlGroup()->getBOOL(mDocStateControl); + setDocked(docked); } + return docked; +} + +void LLFloater::applyPositioning(LLFloater* other, bool on_open) +{ + // Otherwise position according to the positioning code + switch (mPositioning) + { + case LLFloaterEnums::POSITIONING_CENTERED: + center(); + break; + + case LLFloaterEnums::POSITIONING_SPECIFIED: + break; + + case LLFloaterEnums::POSITIONING_CASCADING: + if (!on_open) + { + applyRelativePosition(); + } + // fall through + case LLFloaterEnums::POSITIONING_CASCADE_GROUP: + if (on_open) + { + if (other != NULL && other != this) + { + stackWith(*other); + } + else + { + static const U32 CASCADING_FLOATER_HOFFSET = 0; + static const U32 CASCADING_FLOATER_VOFFSET = 0; + + const LLRect& snap_rect = gFloaterView->getSnapRect(); + + const S32 horizontal_offset = CASCADING_FLOATER_HOFFSET; + const S32 vertical_offset = snap_rect.getHeight() - CASCADING_FLOATER_VOFFSET; + + S32 rect_height = getRect().getHeight(); + setOrigin(horizontal_offset, vertical_offset - rect_height); + + translate(snap_rect.mLeft, snap_rect.mBottom); + } + setFollows(FOLLOWS_TOP | FOLLOWS_LEFT); + } + break; + + case LLFloaterEnums::POSITIONING_RELATIVE: + { + applyRelativePosition(); + + break; + } + default: + // Do nothing + break; + } } void LLFloater::applyTitle() @@ -942,7 +1067,9 @@ BOOL LLFloater::canSnapTo(const LLView* other_view) if (other_view != getParent()) { const LLFloater* other_floaterp = dynamic_cast<const LLFloater*>(other_view); - if (other_floaterp && other_floaterp->getSnapTarget() == getHandle() && mDependents.find(other_floaterp->getHandle()) != mDependents.end()) + if (other_floaterp + && other_floaterp->getSnapTarget() == getHandle() + && mDependents.find(other_floaterp->getHandle()) != mDependents.end()) { // this is a dependent that is already snapped to us, so don't snap back to it return FALSE; @@ -974,6 +1101,14 @@ void LLFloater::handleReshape(const LLRect& new_rect, bool by_user) const LLRect old_rect = getRect(); LLView::handleReshape(new_rect, by_user); + if (by_user && !isMinimized()) + { + storeRectControl(); + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + LLRect screen_rect = calcScreenRect(); + mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); + } + // if not minimized, adjust all snapped dependents to new shape if (!isMinimized()) { @@ -1031,7 +1166,7 @@ void LLFloater::setMinimized(BOOL minimize) if (minimize == mMinimized) return; - if(mMinimizeSignal) + if (mMinimizeSignal) { (*mMinimizeSignal)(this, LLSD(minimize)); } @@ -1063,10 +1198,6 @@ void LLFloater::setMinimized(BOOL minimize) mButtonsEnabled[BUTTON_RESTORE] = TRUE; } - if (mDragHandle) - { - mDragHandle->setVisible(TRUE); - } setBorderVisible(TRUE); for(handle_set_iter_t dependent_it = mDependents.begin(); @@ -1153,6 +1284,7 @@ void LLFloater::setMinimized(BOOL minimize) // Reshape *after* setting mMinimized reshape( mExpandedRect.getWidth(), mExpandedRect.getHeight(), TRUE ); + applyPositioning(NULL, false); } make_ui_sound("UISndWindowClose"); @@ -1193,6 +1325,7 @@ void LLFloater::setFocus( BOOL b ) last_focus->setFocus(TRUE); } } + updateTransparency(b ? TT_ACTIVE : TT_INACTIVE); } // virtual @@ -1216,19 +1349,9 @@ void LLFloater::setIsChrome(BOOL is_chrome) mButtons[BUTTON_CLOSE]->setToolTip(LLStringExplicit(getButtonTooltip(Params(), BUTTON_CLOSE, is_chrome))); } - // no titles displayed on "chrome" floaters - if (mDragHandle) - mDragHandle->setTitleVisible(!is_chrome); - LLPanel::setIsChrome(is_chrome); } -void LLFloater::setTitleVisible(bool visible) -{ - if (mDragHandle) - mDragHandle->setTitleVisible(visible); -} - // Change the draw style to account for the foreground state. void LLFloater::setForeground(BOOL front) { @@ -1316,7 +1439,10 @@ void LLFloater::moveResizeHandlesToFront() BOOL LLFloater::isFrontmost() { - return gFloaterView && gFloaterView->getFrontmost() == this && getVisible(); + LLFloaterView* floater_view = getParentByType<LLFloaterView>(); + return getVisible() + && (floater_view + && floater_view->getFrontmost() == this); } void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition) @@ -1389,6 +1515,7 @@ BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) if(offerClickToButton(x, y, mask, BUTTON_CLOSE)) return TRUE; if(offerClickToButton(x, y, mask, BUTTON_RESTORE)) return TRUE; if(offerClickToButton(x, y, mask, BUTTON_TEAR_OFF)) return TRUE; + if(offerClickToButton(x, y, mask, BUTTON_DOCK)) return TRUE; // Otherwise pass to drag handle for movement return mDragHandle->handleMouseDown(x, y, mask); @@ -1465,6 +1592,9 @@ void LLFloater::setFrontmost(BOOL take_focus) // there are more than one floater view // so we need to query our parent directly ((LLFloaterView*)getParent())->bringToFront(this, take_focus); + + // Make sure to set the appropriate transparency type (STORM-732). + updateTransparency(hasFocus() || getIsChrome() ? TT_ACTIVE : TT_INACTIVE); } } @@ -1491,6 +1621,13 @@ void LLFloater::setDocked(bool docked, bool pop_on_undock) { mDocked = docked; mButtonsEnabled[BUTTON_DOCK] = !mDocked; + + if (mDocked) + { + setMinimized(FALSE); + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + } + updateTitleButtons(); storeDockStateControl(); @@ -1522,7 +1659,7 @@ void LLFloater::onClickTearOff(LLFloater* self) self->openFloater(self->getKey()); // only force position for floaters that don't have that data saved - if (self->mRectControl.size() <= 1) + if (self->mRectControl.empty()) { new_rect.setLeftTopAndSize(host_floater->getRect().mLeft + 5, host_floater->getRect().mTop - floater_header_size - 5, self->getRect().getWidth(), self->getRect().getHeight()); self->setRect(new_rect); @@ -1574,18 +1711,19 @@ void LLFloater::onClickHelp( LLFloater* self ) LLFloater* LLFloater::getClosableFloaterFromFocus() { LLFloater* focused_floater = NULL; - - handle_map_iter_t iter; - for(iter = sFloaterMap.begin(); iter != sFloaterMap.end(); ++iter) + LLInstanceTracker<LLFloater>::instance_iter it = beginInstances(); + LLInstanceTracker<LLFloater>::instance_iter end_it = endInstances(); + for (; it != end_it; ++it) { - focused_floater = iter->second; - if (focused_floater->hasFocus()) + if (it->hasFocus()) { + LLFloater& floater = *it; + focused_floater = &floater; break; } } - if (iter == sFloaterMap.end()) + if (it == endInstances()) { // nothing found, return return NULL; @@ -1652,7 +1790,7 @@ void LLFloater::onClickCloseBtn() // virtual void LLFloater::draw() { - mCurrentTransparency = hasFocus() ? sActiveFloaterTransparency : sInactiveFloaterTransparency; + const F32 alpha = getCurrentTransparency(); // draw background if( isBackgroundVisible() ) @@ -1684,12 +1822,12 @@ void LLFloater::draw() if (image) { // We're using images for this floater's backgrounds - image->draw(getLocalRect(), overlay_color % mCurrentTransparency); + image->draw(getLocalRect(), overlay_color % alpha); } else { // We're not using images, use old-school flat colors - gl_rect_2d( left, top, right, bottom, color % mCurrentTransparency ); + gl_rect_2d( left, top, right, bottom, color % alpha ); // draw highlight on title bar to indicate focus. RDW if(hasFocus() @@ -1700,8 +1838,8 @@ void LLFloater::draw() const LLFontGL* font = LLFontGL::getFontSansSerif(); LLRect r = getRect(); - gl_rect_2d_offset_local(0, r.getHeight(), r.getWidth(), r.getHeight() - (S32)font->getLineHeight() - 1, - titlebar_focus_color % mCurrentTransparency, 0, TRUE); + gl_rect_2d_offset_local(0, r.getHeight(), r.getWidth(), r.getHeight() - font->getLineHeight() - 1, + titlebar_focus_color % alpha, 0, TRUE); } } } @@ -1729,7 +1867,7 @@ void LLFloater::draw() { drawChild(mButtons[i]); } - drawChild(mDragHandle); + drawChild(mDragHandle, 0, 0, TRUE); } else { @@ -1767,10 +1905,32 @@ void LLFloater::drawShadow(LLPanel* panel) shadow_color.mV[VALPHA] *= 0.5f; } gl_drop_shadow(left, top, right, bottom, - shadow_color % mCurrentTransparency, + shadow_color % getCurrentTransparency(), llround(shadow_offset)); } +void LLFloater::updateTransparency(LLView* view, ETypeTransparency transparency_type) +{ + child_list_t children = *view->getChildList(); + child_list_t::iterator it = children.begin(); + + LLUICtrl* ctrl = dynamic_cast<LLUICtrl*>(view); + if (ctrl) + { + ctrl->setTransparencyType(transparency_type); + } + + for(; it != children.end(); ++it) + { + updateTransparency(*it, transparency_type); + } +} + +void LLFloater::updateTransparency(ETypeTransparency transparency_type) +{ + updateTransparency(this, transparency_type); +} + void LLFloater::setCanMinimize(BOOL can_minimize) { // if removing minimize/restore button programmatically, @@ -1824,6 +1984,12 @@ void LLFloater::setCanDrag(BOOL can_drag) } } +bool LLFloater::getCanDrag() +{ + return mDragHandle->getEnabled(); +} + + void LLFloater::updateTitleButtons() { static LLUICachedControl<S32> floater_close_box_size ("UIFloaterCloseBoxSize", 0); @@ -2028,25 +2194,20 @@ static LLDefaultChildRegistry::Register<LLFloaterView> r("floater_view"); LLFloaterView::LLFloaterView (const Params& p) : LLUICtrl (p), - mFocusCycleMode(FALSE), mMinimizePositionVOffset(0), mSnapOffsetBottom(0), mSnapOffsetRight(0) { + mSnapView = getHandle(); } // By default, adjust vertical. void LLFloaterView::reshape(S32 width, S32 height, BOOL called_from_parent) { - reshapeFloater(width, height, called_from_parent, ADJUST_VERTICAL_YES); -} + LLView::reshape(width, height, called_from_parent); -// When reshaping this view, make the floaters follow their closest edge. -void LLFloaterView::reshapeFloater(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical) -{ - S32 old_width = getRect().getWidth(); - S32 old_height = getRect().getHeight(); + mLastSnapRect = getSnapRect(); for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { @@ -2054,70 +2215,52 @@ void LLFloaterView::reshapeFloater(S32 width, S32 height, BOOL called_from_paren LLFloater* floaterp = (LLFloater*)viewp; if (floaterp->isDependent()) { - // dependents use same follow flags as their "dependee" + // dependents are moved with their "dependee" continue; } - // Make if follow the edge it is closest to - U32 follow_flags = 0x0; - - if (floaterp->isMinimized()) + if (!floaterp->isMinimized() && floaterp->getCanDrag()) { - follow_flags |= (FOLLOWS_LEFT | FOLLOWS_TOP); - } - else - { - LLRect r = floaterp->getRect(); + LLRect old_rect = floaterp->getRect(); + floaterp->applyPositioning(NULL, false); + LLRect new_rect = floaterp->getRect(); - // Compute absolute distance from each edge of screen - S32 left_offset = llabs(r.mLeft - 0); - S32 right_offset = llabs(old_width - r.mRight); + //LLRect r = floaterp->getRect(); - S32 top_offset = llabs(old_height - r.mTop); - S32 bottom_offset = llabs(r.mBottom - 0); + //// Compute absolute distance from each edge of screen + //S32 left_offset = llabs(r.mLeft - 0); + //S32 right_offset = llabs(old_right - r.mRight); + //S32 top_offset = llabs(old_top - r.mTop); + //S32 bottom_offset = llabs(r.mBottom - 0); - if (left_offset < right_offset) - { - follow_flags |= FOLLOWS_LEFT; - } - else - { - follow_flags |= FOLLOWS_RIGHT; - } + S32 translate_x = new_rect.mLeft - old_rect.mLeft; + S32 translate_y = new_rect.mBottom - old_rect.mBottom; - // "No vertical adjustment" usually means that the bottom of the view - // has been pushed up or down. Hence we want the floaters to follow - // the top. - if (!adjust_vertical) - { - follow_flags |= FOLLOWS_TOP; - } - else if (top_offset < bottom_offset) - { - follow_flags |= FOLLOWS_TOP; - } - else - { - follow_flags |= FOLLOWS_BOTTOM; - } - } + //if (left_offset > right_offset) + //{ + // translate_x = new_right - old_right; + //} - floaterp->setFollows(follow_flags); + //if (top_offset < bottom_offset) + //{ + // translate_y = new_top - old_top; + //} - //RN: all dependent floaters copy follow behavior of "parent" - for(LLFloater::handle_set_iter_t dependent_it = floaterp->mDependents.begin(); - dependent_it != floaterp->mDependents.end(); ++dependent_it) - { - LLFloater* dependent_floaterp = dependent_it->get(); - if (dependent_floaterp) + // don't reposition immovable floaters + //if (floaterp->getCanDrag()) + //{ + // floaterp->translate(translate_x, translate_y); + //} + BOOST_FOREACH(LLHandle<LLFloater> dependent_floater, floaterp->mDependents) { - dependent_floaterp->setFollows(follow_flags); + if (dependent_floater.get()) + { + dependent_floater.get()->translate(translate_x, translate_y); + } } } } - - LLView::reshape(width, height, called_from_parent); } @@ -2438,6 +2581,52 @@ void LLFloaterView::closeAllChildren(bool app_quitting) } } +void LLFloaterView::hiddenFloaterClosed(LLFloater* floater) +{ + for (hidden_floaters_t::iterator it = mHiddenFloaters.begin(), end_it = mHiddenFloaters.end(); + it != end_it; + ++it) + { + if (it->first.get() == floater) + { + it->second.disconnect(); + mHiddenFloaters.erase(it); + break; + } + } +} + +void LLFloaterView::hideAllFloaters() +{ + child_list_t child_list = *(getChildList()); + + for (child_list_iter_t it = child_list.begin(); it != child_list.end(); ++it) + { + LLFloater* floaterp = dynamic_cast<LLFloater*>(*it); + if (floaterp && floaterp->getVisible()) + { + floaterp->setVisible(false); + boost::signals2::connection connection = floaterp->mCloseSignal.connect(boost::bind(&LLFloaterView::hiddenFloaterClosed, this, floaterp)); + mHiddenFloaters.push_back(std::make_pair(floaterp->getHandle(), connection)); + } + } +} + +void LLFloaterView::showHiddenFloaters() +{ + for (hidden_floaters_t::iterator it = mHiddenFloaters.begin(), end_it = mHiddenFloaters.end(); + it != end_it; + ++it) + { + LLFloater* floaterp = it->first.get(); + if (floaterp) + { + floaterp->setVisible(true); + } + it->second.disconnect(); + } + mHiddenFloaters.clear(); +} BOOL LLFloaterView::allChildrenClosed() { @@ -2471,6 +2660,12 @@ void LLFloaterView::shiftFloaters(S32 x_offset, S32 y_offset) void LLFloaterView::refresh() { + LLRect snap_rect = getSnapRect(); + if (snap_rect != mLastSnapRect) + { + reshape(getRect().getWidth(), getRect().getHeight(), TRUE); + } + // Constrain children to be entirely on the screen for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { @@ -2483,6 +2678,8 @@ void LLFloaterView::refresh() } } +const S32 FLOATER_MIN_VISIBLE_PIXELS = 16; + void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_outside) { if (floater->getParent() != this) @@ -2536,7 +2733,7 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out } // move window fully onscreen - if (floater->translateIntoRect( getLocalRect(), allow_partial_outside )) + if (floater->translateIntoRect( getSnapRect(), allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX )) { floater->clearSnapTarget(); } @@ -2570,9 +2767,13 @@ void LLFloaterView::draw() LLRect LLFloaterView::getSnapRect() const { - LLRect snap_rect = getRect(); - snap_rect.mBottom += mSnapOffsetBottom; - snap_rect.mRight -= mSnapOffsetRight; + LLRect snap_rect = getLocalRect(); + + LLView* snap_view = mSnapView.get(); + if (snap_view) + { + snap_view->localRectToOtherView(snap_view->getLocalRect(), &snap_rect, this); + } return snap_rect; } @@ -2746,9 +2947,11 @@ void LLFloater::setInstanceName(const std::string& name) std::string ctrl_name = getControlName(mInstanceName, mKey); // save_rect and save_visibility only apply to registered floaters - if (!mRectControl.empty()) + if (mSaveRect) { mRectControl = LLFloaterReg::declareRectControl(ctrl_name); + mPosXControl = LLFloaterReg::declarePosXControl(ctrl_name); + mPosYControl = LLFloaterReg::declarePosYControl(ctrl_name); } if (!mVisibilityControl.empty()) { @@ -2758,7 +2961,6 @@ void LLFloater::setInstanceName(const std::string& name) { mDocStateControl = LLFloaterReg::declareDockStateControl(ctrl_name); } - } } @@ -2805,6 +3007,12 @@ void LLFloater::initFromParams(const LLFloater::Params& p) // control_name, tab_stop, focus_lost_callback, initial_value, rect, enabled, visible LLPanel::initFromParams(p); + // override any follows flags + if (mPositioning != LLFloaterEnums::POSITIONING_SPECIFIED) + { + setFollows(FOLLOWS_NONE); + } + mTitle = p.title; mShortTitle = p.short_title; applyTitle(); @@ -2820,18 +3028,15 @@ void LLFloater::initFromParams(const LLFloater::Params& p) mHeaderHeight = p.header_height; mLegacyHeaderHeight = p.legacy_header_height; mSingleInstance = p.single_instance; - mAutoTile = p.auto_tile; - mOpenCentered = p.open_centered; + mReuseInstance = p.reuse_instance.isProvided() ? p.reuse_instance : p.single_instance; - if (p.save_rect) - { - mRectControl = "t"; // flag to build mRectControl name once mInstanceName is set - } + mPositioning = p.positioning; + + mSaveRect = p.save_rect; if (p.save_visibility) { mVisibilityControl = "t"; // flag to build mVisibilityControl name once mInstanceName is set } - if(p.save_dock_state) { mDocStateControl = "t"; // flag to build mDocStateControl name once mInstanceName is set @@ -2840,12 +3045,17 @@ void LLFloater::initFromParams(const LLFloater::Params& p) // open callback if (p.open_callback.isProvided()) { - mOpenSignal.connect(initCommitCallback(p.open_callback)); + setOpenCallback(initCommitCallback(p.open_callback)); } // close callback if (p.close_callback.isProvided()) { - mCloseSignal.connect(initCommitCallback(p.close_callback)); + setCloseCallback(initCommitCallback(p.close_callback)); + } + + if (mDragHandle) + { + mDragHandle->setTitleVisible(p.show_title); } } @@ -2855,14 +3065,67 @@ boost::signals2::connection LLFloater::setMinimizeCallback( const commit_signal_ return mMinimizeSignal->connect(cb); } +boost::signals2::connection LLFloater::setOpenCallback( const commit_signal_t::slot_type& cb ) +{ + return mOpenSignal.connect(cb); +} + +boost::signals2::connection LLFloater::setCloseCallback( const commit_signal_t::slot_type& cb ) +{ + return mCloseSignal.connect(cb); +} + LLFastTimer::DeclareTimer POST_BUILD("Floater Post Build"); +static LLFastTimer::DeclareTimer FTM_EXTERNAL_FLOATER_LOAD("Load Extern Floater Reference"); bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::string& filename, LLXMLNodePtr output_node) { - Params params(LLUICtrlFactory::getDefaultParams<LLFloater>()); + Params default_params(LLUICtrlFactory::getDefaultParams<LLFloater>()); + Params params(default_params); + LLXUIParser parser; parser.readXUI(node, params, filename); // *TODO: Error checking + std::string xml_filename = params.filename; + + if (!xml_filename.empty()) + { + LLXMLNodePtr referenced_xml; + + if (output_node) + { + //if we are exporting, we want to export the current xml + //not the referenced xml + Params output_params; + parser.readXUI(node, output_params, LLUICtrlFactory::getInstance()->getCurFileName()); + setupParamsForExport(output_params, parent); + output_node->setName(node->getName()->mString); + parser.writeXUI(output_node, output_params, &default_params); + return TRUE; + } + + LLUICtrlFactory::instance().pushFileName(xml_filename); + + LLFastTimer _(FTM_EXTERNAL_FLOATER_LOAD); + if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) + { + llwarns << "Couldn't parse panel from: " << xml_filename << llendl; + + return FALSE; + } + + Params referenced_params; + parser.readXUI(referenced_xml, referenced_params, LLUICtrlFactory::getInstance()->getCurFileName()); + params.fillFrom(referenced_params); + + // add children using dimensions from referenced xml for consistent layout + setShape(params.rect); + LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); + + LLUICtrlFactory::instance().popFileName(); + } + + if (output_node) { Params output_params(params); @@ -2883,11 +3146,10 @@ bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::str { params.rect.left.set(0); } - params.from_xui = true; - applyXUILayout(params, parent); + applyXUILayout(params, parent, parent == gFloaterView ? gFloaterView->getSnapRect() : parent->getLocalRect()); initFromParams(params); - + initFloater(params); LLMultiFloater* last_host = LLFloater::getFloaterHost(); @@ -3025,3 +3287,159 @@ bool LLFloater::buildFromFile(const std::string& filename, LLXMLNodePtr output_n return res; } + +void LLFloater::stackWith(LLFloater& other) +{ + static LLUICachedControl<S32> floater_offset ("UIFloaterOffset", 16); + + LLRect next_rect; + if (other.getHost()) + { + next_rect = other.getHost()->getRect(); + } + else + { + next_rect = other.getRect(); + } + next_rect.translate(floater_offset, -floater_offset); + + next_rect.setLeftTopAndSize(next_rect.mLeft, next_rect.mTop, getRect().getWidth(), getRect().getHeight()); + + setShape(next_rect); + + if (!other.getHost()) + { + other.mPositioning = LLFloaterEnums::POSITIONING_CASCADE_GROUP; + other.setFollows(FOLLOWS_LEFT | FOLLOWS_TOP); + } +} + +void LLFloater::applyRelativePosition() +{ + LLRect snap_rect = gFloaterView->getSnapRect(); + LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); + snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); + LLRect floater_screen_rect = calcScreenRect(); + + LLCoordGL new_center = mPosition.convert(); + LLCoordGL cur_center(floater_screen_rect.getCenterX(), floater_screen_rect.getCenterY()); + translate(new_center.mX - cur_center.mX, new_center.mY - cur_center.mY); +} + + +LLCoordFloater::LLCoordFloater(F32 x, F32 y, LLFloater& floater) +: coord_t((S32)x, (S32)y) +{ + mFloater = floater.getHandle(); +} + + +LLCoordFloater::LLCoordFloater(const LLCoordCommon& other, LLFloater& floater) +{ + mFloater = floater.getHandle(); + convertFromCommon(other); +} + +LLCoordFloater& LLCoordFloater::operator=(const LLCoordFloater& other) +{ + mFloater = other.mFloater; + coord_t::operator =(other); + return *this; +} + +void LLCoordFloater::setFloater(LLFloater& floater) +{ + mFloater = floater.getHandle(); +} + +bool LLCoordFloater::operator==(const LLCoordFloater& other) const +{ + return mX == other.mX && mY == other.mY && mFloater == other.mFloater; +} + +LLCoordCommon LL_COORD_FLOATER::convertToCommon() const +{ + const LLCoordFloater& self = static_cast<const LLCoordFloater&>(LLCoordFloater::getTypedCoords(*this)); + + LLRect snap_rect = gFloaterView->getSnapRect(); + LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); + snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); + + LLFloater* floaterp = mFloater.get(); + S32 floater_width = floaterp ? floaterp->getRect().getWidth() : 0; + S32 floater_height = floaterp ? floaterp->getRect().getHeight() : 0; + LLCoordCommon out; + if (self.mX < -0.5f) + { + out.mX = llround(rescale(self.mX, -1.f, -0.5f, snap_rect.mLeft - (floater_width - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mLeft)); + } + else if (self.mX > 0.5f) + { + out.mX = llround(rescale(self.mX, 0.5f, 1.f, snap_rect.mRight - floater_width, snap_rect.mRight - FLOATER_MIN_VISIBLE_PIXELS)); + } + else + { + out.mX = llround(rescale(self.mX, -0.5f, 0.5f, snap_rect.mLeft, snap_rect.mRight - floater_width)); + } + + if (self.mY < -0.5f) + { + out.mY = llround(rescale(self.mY, -1.f, -0.5f, snap_rect.mBottom - (floater_height - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mBottom)); + } + else if (self.mY > 0.5f) + { + out.mY = llround(rescale(self.mY, 0.5f, 1.f, snap_rect.mTop - floater_height, snap_rect.mTop - FLOATER_MIN_VISIBLE_PIXELS)); + } + else + { + out.mY = llround(rescale(self.mY, -0.5f, 0.5f, snap_rect.mBottom, snap_rect.mTop - floater_height)); + } + + // return center point instead of lower left + out.mX += floater_width / 2; + out.mY += floater_height / 2; + + return out; +} + +void LL_COORD_FLOATER::convertFromCommon(const LLCoordCommon& from) +{ + LLCoordFloater& self = static_cast<LLCoordFloater&>(LLCoordFloater::getTypedCoords(*this)); + LLRect snap_rect = gFloaterView->getSnapRect(); + LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); + snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); + + + LLFloater* floaterp = mFloater.get(); + S32 floater_width = floaterp ? floaterp->getRect().getWidth() : 0; + S32 floater_height = floaterp ? floaterp->getRect().getHeight() : 0; + + S32 from_x = from.mX - floater_width / 2; + S32 from_y = from.mY - floater_height / 2; + + if (from_x < snap_rect.mLeft) + { + self.mX = rescale(from_x, snap_rect.mLeft - (floater_width - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mLeft, -1.f, -0.5f); + } + else if (from_x + floater_width > snap_rect.mRight) + { + self.mX = rescale(from_x, snap_rect.mRight - floater_width, snap_rect.mRight - FLOATER_MIN_VISIBLE_PIXELS, 0.5f, 1.f); + } + else + { + self.mX = rescale(from_x, snap_rect.mLeft, snap_rect.mRight - floater_width, -0.5f, 0.5f); + } + + if (from_y < snap_rect.mBottom) + { + self.mY = rescale(from_y, snap_rect.mBottom - (floater_height - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mBottom, -1.f, -0.5f); + } + else if (from_y + floater_height > snap_rect.mTop) + { + self.mY = rescale(from_y, snap_rect.mTop - floater_height, snap_rect.mTop - FLOATER_MIN_VISIBLE_PIXELS, 0.5f, 1.f); + } + else + { + self.mY = rescale(from_y, snap_rect.mBottom, snap_rect.mTop - floater_height, -0.5f, 0.5f); + } +} diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index fa806bb632..64d6dcea04 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -35,6 +35,7 @@ #include "lluuid.h" //#include "llnotificationsutil.h" #include <set> +#include <boost/signals2.hpp> class LLDragHandle; class LLResizeHandle; @@ -59,11 +60,66 @@ const BOOL CLOSE_NO = FALSE; const BOOL ADJUST_VERTICAL_YES = TRUE; const BOOL ADJUST_VERTICAL_NO = FALSE; -class LLFloater : public LLPanel +namespace LLFloaterEnums { -friend class LLFloaterView; -friend class LLFloaterReg; -friend class LLMultiFloater; + enum EOpenPositioning + { + POSITIONING_RELATIVE, + POSITIONING_CASCADING, + POSITIONING_CASCADE_GROUP, + POSITIONING_CENTERED, + POSITIONING_SPECIFIED, + POSITIONING_COUNT + }; +} + +namespace LLInitParam +{ + template<> + struct TypeValues<LLFloaterEnums::EOpenPositioning> : public TypeValuesHelper<LLFloaterEnums::EOpenPositioning> + { + static void declareValues(); + }; +} + +struct LL_COORD_FLOATER +{ + typedef F32 value_t; + + LLCoordCommon convertToCommon() const; + void convertFromCommon(const LLCoordCommon& from); +protected: + LLHandle<LLFloater> mFloater; +}; + +struct LLCoordFloater : LLCoord<LL_COORD_FLOATER> +{ + typedef LLCoord<LL_COORD_FLOATER> coord_t; + + LLCoordFloater() {} + LLCoordFloater(F32 x, F32 y, LLFloater& floater); + LLCoordFloater(const LLCoordCommon& other, LLFloater& floater); + + LLCoordFloater& operator=(const LLCoordCommon& other) + { + convertFromCommon(other); + return *this; + } + + LLCoordFloater& operator=(const LLCoordFloater& other); + + bool operator==(const LLCoordFloater& other) const; + bool operator!=(const LLCoordFloater& other) const { return !(*this == other); } + + void setFloater(LLFloater& floater); +}; + +class LLFloater : public LLPanel, public LLInstanceTracker<LLFloater> +{ + friend class LLFloaterView; + friend class LLFloaterReg; + friend class LLMultiFloater; + public: struct KeyCompare { @@ -95,7 +151,7 @@ public: short_title; Optional<bool> single_instance, - auto_tile, + reuse_instance, can_resize, can_minimize, can_close, @@ -105,7 +161,10 @@ public: save_visibility, save_dock_state, can_dock, - open_centered; + show_title; + + Optional<LLFloaterEnums::EOpenPositioning> positioning; + Optional<S32> header_height, legacy_header_height; // HACK see initFromXML() @@ -125,6 +184,8 @@ public: Optional<CommitCallbackParam> open_callback, close_callback; + + Ignored follows; Params(); }; @@ -144,12 +205,14 @@ public: bool buildFromFile(const std::string &filename, LLXMLNodePtr output_node = NULL); boost::signals2::connection setMinimizeCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setOpenCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setCloseCallback( const commit_signal_t::slot_type& cb ); void initFromParams(const LLFloater::Params& p); bool initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::string& filename, LLXMLNodePtr output_node = NULL); /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); - /*virtual*/ BOOL canSnapTo(const LLView* other_view); + /*virtual*/ BOOL canSnapTo(const LLView* other_view); /*virtual*/ void setSnappedTo(const LLView* snap_view); /*virtual*/ void setFocus( BOOL b ); /*virtual*/ void setIsChrome(BOOL is_chrome); @@ -178,7 +241,6 @@ public: std::string getTitle() const; void setShortTitle( const std::string& short_title ); std::string getShortTitle() const; - void setTitleVisible(bool visible); virtual void setMinimized(BOOL b); void moveResizeHandlesToFront(); void addDependentFloater(LLFloater* dependent, BOOL reposition = TRUE); @@ -202,12 +264,11 @@ public: void setCanTearOff(BOOL can_tear_off); virtual void setCanResize(BOOL can_resize); void setCanDrag(BOOL can_drag); + bool getCanDrag(); void setHost(LLMultiFloater* host); BOOL isResizable() const { return mResizable; } void setResizeLimits( S32 min_width, S32 min_height ); void getResizeLimits( S32* min_width, S32* min_height ) { *min_width = mMinWidth; *min_height = mMinHeight; } - LLRect getSavedRect() const; - bool hasSavedRect() const; static std::string getControlName(const std::string& name, const LLSD& key); static LLControlGroup* getControlGroup(); @@ -250,9 +311,9 @@ public: void clearSnapTarget() { mSnappedTo.markDead(); } LLHandle<LLFloater> getSnapTarget() const { return mSnappedTo; } - LLHandle<LLFloater> getHandle() const { return mHandle; } + LLHandle<LLFloater> getHandle() const { return getDerivedHandle<LLFloater>(); } const LLSD& getKey() { return mKey; } - BOOL matchesKey(const LLSD& key) { return mSingleInstance || KeyCompare::equate(key, mKey); } + virtual bool matchesKey(const LLSD& key) { return mSingleInstance || KeyCompare::equate(key, mKey); } const std::string& getInstanceName() { return mInstanceName; } @@ -284,15 +345,22 @@ public: static void setFloaterHost(LLMultiFloater* hostp) {sHostp = hostp; } static LLMultiFloater* getFloaterHost() {return sHostp; } + + void updateTransparency(ETypeTransparency transparency_type); + void enableResizeCtrls(bool enable, bool width = true, bool height = true); + + bool isPositioning(LLFloaterEnums::EOpenPositioning p) const { return (p == mPositioning); } protected: + void applyControlsAndPosition(LLFloater* other); - void setRectControl(const std::string& rectname) { mRectControl = rectname; }; + void stackWith(LLFloater& other); - virtual void applySavedVariables(); + virtual bool applyRectControl(); + bool applyDockState(); + void applyPositioning(LLFloater* other, bool on_open); + void applyRelativePosition(); - void applyRectControl(); - void applyDockState(); void storeRectControl(); void storeVisibilityControl(); void storeDockStateControl(); @@ -337,12 +405,12 @@ private: BOOL offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index); void addResizeCtrls(); void layoutResizeCtrls(); - void enableResizeCtrls(bool enable); void addDragHandle(); void layoutDragHandle(); // repair layout static void updateActiveFloaterTransparency(); static void updateInactiveFloaterTransparency(); + void updateTransparency(LLView* view, ETypeTransparency transparency_type); public: // Called when floater is opened, passes mKey @@ -356,7 +424,10 @@ public: commit_signal_t* mMinimizeSignal; protected: + bool mSaveRect; std::string mRectControl; + std::string mPosXControl; + std::string mPosYControl; std::string mVisibilityControl; std::string mDocStateControl; LLSD mKey; // Key used for retrieving instances; set (for now) by LLFLoaterReg @@ -373,15 +444,17 @@ private: LLUIString mShortTitle; BOOL mSingleInstance; // TRUE if there is only ever one instance of the floater + bool mReuseInstance; // true if we want to hide the floater when we close it instead of destroying it std::string mInstanceName; // Store the instance name so we can remove ourselves from the list - BOOL mAutoTile; // TRUE if placement of new instances tiles BOOL mCanTearOff; BOOL mCanMinimize; BOOL mCanClose; BOOL mDragOnLeft; BOOL mResizable; - bool mOpenCentered; + + LLFloaterEnums::EOpenPositioning mPositioning; + LLCoordFloater mPosition; S32 mMinWidth; S32 mMinHeight; @@ -411,11 +484,6 @@ private: bool mDocked; bool mTornOff; - F32 mCurrentTransparency; - - static F32 sActiveFloaterTransparency; - static F32 sInactiveFloaterTransparency; - static LLMultiFloater* sHostp; static BOOL sQuitting; static std::string sButtonNames[BUTTON_COUNT]; @@ -425,18 +493,9 @@ private: typedef void(*click_callback)(LLFloater*); static click_callback sButtonCallbacks[BUTTON_COUNT]; - typedef std::map<LLHandle<LLFloater>, LLFloater*> handle_map_t; - typedef std::map<LLHandle<LLFloater>, LLFloater*>::iterator handle_map_iter_t; - static handle_map_t sFloaterMap; - - std::vector<LLHandle<LLView> > mMinimizedHiddenChildren; - BOOL mHasBeenDraggedWhileMinimized; S32 mPreviousMinimizedBottom; S32 mPreviousMinimizedLeft; - -// LLFloaterNotificationContext* mNotificationContext; - LLRootHandle<LLFloater> mHandle; }; @@ -456,8 +515,6 @@ protected: public: /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); - void reshapeFloater(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical); - /*virtual*/ void draw(); /*virtual*/ LLRect getSnapRect() const; /*virtual*/ void refresh(); @@ -486,6 +543,10 @@ public: BOOL allChildrenClosed(); void shiftFloaters(S32 x_offset, S32 y_offset); + void hideAllFloaters(); + void showHiddenFloaters(); + + LLFloater* getFrontmost() const; LLFloater* getBackmost() const; LLFloater* getParentFloater(LLView* viewp) const; @@ -497,14 +558,19 @@ public: // value is not defined. S32 getZOrder(LLFloater* child); - void setSnapOffsetBottom(S32 offset) { mSnapOffsetBottom = offset; } - void setSnapOffsetRight(S32 offset) { mSnapOffsetRight = offset; } + void setFloaterSnapView(LLHandle<LLView> snap_view) {mSnapView = snap_view; } private: + void hiddenFloaterClosed(LLFloater* floater); + + LLRect mLastSnapRect; + LLHandle<LLView> mSnapView; BOOL mFocusCycleMode; S32 mSnapOffsetBottom; S32 mSnapOffsetRight; S32 mMinimizePositionVOffset; + typedef std::vector<std::pair<LLHandle<LLFloater>, boost::signals2::connection> > hidden_floaters_t; + hidden_floaters_t mHiddenFloaters; }; // diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index 4677d535db..9115eb7174 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -57,29 +57,59 @@ void LLFloaterReg::add(const std::string& name, const std::string& filename, con } //static -LLRect LLFloaterReg::getFloaterRect(const std::string& name) +LLFloater* LLFloaterReg::getLastFloaterInGroup(const std::string& name) { - LLRect rect; const std::string& groupname = sGroupMap[name]; if (!groupname.empty()) { instance_list_t& list = sInstanceMap[groupname]; if (!list.empty()) { - static LLUICachedControl<S32> floater_offset ("UIFloaterOffset", 16); - LLFloater* last_floater = list.back(); - if (last_floater->getHost()) + for (instance_list_t::reverse_iterator iter = list.rbegin(); iter != list.rend(); ++iter) { - rect = last_floater->getHost()->getRect(); + LLFloater* inst = *iter; + + if (inst->getVisible() && !inst->isMinimized()) + { + return inst; + } } - else + } + } + return NULL; +} + +LLFloater* LLFloaterReg::getLastFloaterCascading() +{ + LLRect candidate_rect; + candidate_rect.mTop = 100000; + LLFloater* candidate_floater = NULL; + + std::map<std::string,std::string>::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end(); + for( ; it != it_end; ++it) + { + const std::string& group_name = it->second; + + instance_list_t& instances = sInstanceMap[group_name]; + + for (instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); ++iter) + { + LLFloater* inst = *iter; + + if (inst->getVisible() + && (inst->isPositioning(LLFloaterEnums::POSITIONING_CASCADING) + || inst->isPositioning(LLFloaterEnums::POSITIONING_CASCADE_GROUP))) { - rect = last_floater->getRect(); + if (candidate_rect.mTop > inst->getRect().mTop) + { + candidate_floater = inst; + candidate_rect = inst->getRect(); + } } - rect.translate(floater_offset, -floater_offset); } } - return rect; + + return candidate_floater; } //static @@ -117,34 +147,33 @@ LLFloater* LLFloaterReg::getInstance(const std::string& name, const LLSD& key) if (!groupname.empty()) { instance_list_t& list = sInstanceMap[groupname]; - int index = list.size(); res = build_func(key); - + if (!res) + { + llwarns << "Failed to build floater type: '" << name << "'." << llendl; + return NULL; + } bool success = res->buildFromFile(xui_file, NULL); if (!success) { llwarns << "Failed to build floater type: '" << name << "'." << llendl; return NULL; } - + // Note: key should eventually be a non optional LLFloater arg; for now, set mKey to be safe - res->mKey = key; - res->setInstanceName(name); - res->applySavedVariables(); // Can't apply rect and dock state until setting instance name - if (res->mAutoTile && !res->getHost() && index > 0) + if (res->mKey.isUndefined()) { - const LLRect& cur_rect = res->getRect(); - LLRect next_rect = getFloaterRect(groupname); - next_rect.setLeftTopAndSize(next_rect.mLeft, next_rect.mTop, cur_rect.getWidth(), cur_rect.getHeight()); - res->setRect(next_rect); - res->setRectControl(LLStringUtil::null); // don't save rect of tiled floaters - gFloaterView->adjustToFitScreen(res, true); - } - else - { - gFloaterView->adjustToFitScreen(res, false); + res->mKey = key; } + res->setInstanceName(name); + + LLFloater *last_floater = (list.empty() ? NULL : list.back()); + + res->applyControlsAndPosition(last_floater); + + gFloaterView->adjustToFitScreen(res, false); + list.push_back(res); } } @@ -331,9 +360,7 @@ void LLFloaterReg::restoreVisibleInstances() //static std::string LLFloaterReg::getRectControlName(const std::string& name) { - std::string res = std::string("floater_rect_") + name; - LLStringUtil::replaceChar( res, ' ', '_' ); - return res; + return std::string("floater_rect_") + getBaseControlName(name); } //static @@ -341,19 +368,48 @@ std::string LLFloaterReg::declareRectControl(const std::string& name) { std::string controlname = getRectControlName(name); LLFloater::getControlGroup()->declareRect(controlname, LLRect(), - llformat("Window Position and Size for %s", name.c_str()), + llformat("Window Size for %s", name.c_str()), TRUE); return controlname; } +std::string LLFloaterReg::declarePosXControl(const std::string& name) +{ + std::string controlname = std::string("floater_pos_") + getBaseControlName(name) + "_x"; + LLFloater::getControlGroup()->declareF32(controlname, + 10.f, + llformat("Window X Position for %s", name.c_str()), + TRUE); + return controlname; +} + +std::string LLFloaterReg::declarePosYControl(const std::string& name) +{ + std::string controlname = std::string("floater_pos_") + getBaseControlName(name) + "_y"; + LLFloater::getControlGroup()->declareF32(controlname, + 10.f, + llformat("Window Y Position for %s", name.c_str()), + TRUE); + + return controlname; +} + + //static std::string LLFloaterReg::getVisibilityControlName(const std::string& name) { - std::string res = std::string("floater_vis_") + name; + return std::string("floater_vis_") + getBaseControlName(name); +} + +//static +std::string LLFloaterReg::getBaseControlName(const std::string& name) +{ + std::string res(name); LLStringUtil::replaceChar( res, ' ', '_' ); return res; } + //static std::string LLFloaterReg::declareVisibilityControl(const std::string& name) { @@ -410,70 +466,71 @@ void LLFloaterReg::registerControlVariables() } } -// Callbacks - -// static -// Call once (i.e use for init callbacks) -void LLFloaterReg::initUICtrlToFloaterVisibilityControl(LLUICtrl* ctrl, const LLSD& sdname) +//static +void LLFloaterReg::toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& key) { - // Get the visibility control name for the floater - std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); - // Set the control value to the floater visibility control (Sets the value as well) - ctrl->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); -} + // + // Floaters controlled by the toolbar behave a bit differently from others. + // Namely they have 3-4 states as defined in the design wiki page here: + // https://wiki.lindenlab.com/wiki/FUI_Button_states + // + // The basic idea is this: + // * If the target floater is minimized, this button press will un-minimize it. + // * Else if the target floater is closed open it. + // * Else if the target floater does not have focus, give it focus. + // * Also, if it is not on top, bring it forward when focus is given. + // * Else the target floater is open, close it. + // -// callback args may use "floatername.key" format -static void parse_name_key(std::string& name, LLSD& key) -{ - std::string instname = name; - std::size_t dotpos = instname.find("."); - if (dotpos != std::string::npos) + std::string name = sdname.asString(); + LLFloater* instance = getInstance(name, key); + + if (!instance) + { + lldebugs << "Unable to get instance of floater '" << name << "'" << llendl; + } + else if (instance->isMinimized()) + { + instance->setMinimized(FALSE); + instance->setVisibleAndFrontmost(); + } + else if (!instance->isShown()) + { + instance->openFloater(key); + instance->setVisibleAndFrontmost(); + } + else if (!instance->isFrontmost()) + { + instance->setVisibleAndFrontmost(); + } + else { - name = instname.substr(0, dotpos); - key = LLSD(instname.substr(dotpos+1, std::string::npos)); + instance->closeFloater(); } } -//static -void LLFloaterReg::showFloaterInstance(const LLSD& sdname) -{ - LLSD key; - std::string name = sdname.asString(); - parse_name_key(name, key); - showInstance(name, key, TRUE); -} -//static -void LLFloaterReg::hideFloaterInstance(const LLSD& sdname) -{ - LLSD key; - std::string name = sdname.asString(); - parse_name_key(name, key); - hideInstance(name, key); -} -//static -void LLFloaterReg::toggleFloaterInstance(const LLSD& sdname) +// static +U32 LLFloaterReg::getVisibleFloaterInstanceCount() { - LLSD key; - std::string name = sdname.asString(); - parse_name_key(name, key); - toggleInstance(name, key); -} + U32 count = 0; -//static -bool LLFloaterReg::floaterInstanceVisible(const LLSD& sdname) -{ - LLSD key; - std::string name = sdname.asString(); - parse_name_key(name, key); - return instanceVisible(name, key); -} + std::map<std::string,std::string>::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end(); + for( ; it != it_end; ++it) + { + const std::string& group_name = it->second; -//static -bool LLFloaterReg::floaterInstanceMinimized(const LLSD& sdname) -{ - LLSD key; - std::string name = sdname.asString(); - parse_name_key(name, key); - LLFloater* instance = findInstance(name, key); - return LLFloater::isShown(instance); + instance_list_t& instances = sInstanceMap[group_name]; + + for (instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); ++iter) + { + LLFloater* inst = *iter; + + if (inst->getVisible() && !inst->isMinimized()) + { + count++; + } + } + } + + return count; } diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h index 94387fb41a..a1e1f8a988 100644 --- a/indra/llui/llfloaterreg.h +++ b/indra/llui/llfloaterreg.h @@ -27,14 +27,9 @@ #define LLFLOATERREG_H /// llcommon -#include "llboost.h" #include "llrect.h" -#include "llstl.h" #include "llsd.h" -/// llui -#include "lluictrl.h" - #include <boost/function.hpp> //******************************************************* @@ -43,6 +38,7 @@ // class LLFloater; +class LLUICtrl; typedef boost::function<LLFloater* (const LLSD& key)> LLFloaterBuildFunc; @@ -90,7 +86,8 @@ public: const std::string& groupname = LLStringUtil::null); // Helpers - static LLRect getFloaterRect(const std::string& name); + static LLFloater* getLastFloaterInGroup(const std::string& name); + static LLFloater* getLastFloaterCascading(); // Find / get (create) / remove / destroy static LLFloater* findInstance(const std::string& name, const LLSD& key = LLSD()); @@ -118,21 +115,18 @@ public: // Control Variables static std::string getRectControlName(const std::string& name); static std::string declareRectControl(const std::string& name); + static std::string declarePosXControl(const std::string& name); + static std::string declarePosYControl(const std::string& name); static std::string getVisibilityControlName(const std::string& name); static std::string declareVisibilityControl(const std::string& name); - + static std::string getBaseControlName(const std::string& name); static std::string declareDockStateControl(const std::string& name); static std::string getDockStateControlName(const std::string& name); static void registerControlVariables(); // Callback wrappers - static void initUICtrlToFloaterVisibilityControl(LLUICtrl* ctrl, const LLSD& sdname); - static void showFloaterInstance(const LLSD& sdname); - static void hideFloaterInstance(const LLSD& sdname); - static void toggleFloaterInstance(const LLSD& sdname); - static bool floaterInstanceVisible(const LLSD& sdname); - static bool floaterInstanceMinimized(const LLSD& sdname); + static void toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& key = LLSD()); // Typed find / get / show template <class T> @@ -155,6 +149,7 @@ public: static void blockShowFloaters(bool value) { sBlockShowFloaters = value;} + static U32 getVisibleFloaterInstanceCount(); }; #endif diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp index 821d4543ae..7525b8cab3 100644 --- a/indra/llui/llfloaterreglistener.cpp +++ b/indra/llui/llfloaterreglistener.cpp @@ -60,6 +60,11 @@ LLFloaterRegListener::LLFloaterRegListener(): "Ask to toggle the state of the floater specified in [\"name\"]", &LLFloaterRegListener::toggleInstance, requiredName); + add("instanceVisible", + "Return on [\"reply\"] an event whose [\"visible\"] indicates the visibility " + "of the floater specified in [\"name\"]", + &LLFloaterRegListener::instanceVisible, + requiredName); LLSD requiredNameButton; requiredNameButton["name"] = LLSD(); requiredNameButton["button"] = LLSD(); @@ -71,9 +76,7 @@ LLFloaterRegListener::LLFloaterRegListener(): void LLFloaterRegListener::getBuildMap(const LLSD& event) const { - // Honor the "reqid" convention by echoing event["reqid"] in our reply packet. - LLReqID reqID(event); - LLSD reply(reqID.makeResponse()); + LLSD reply; // Build an LLSD map that mirrors sBuildMap. Since we have no good way to // represent a C++ callable in LLSD, the only part of BuildData we can // store is the filename. For each LLSD map entry, it would be more @@ -86,7 +89,7 @@ void LLFloaterRegListener::getBuildMap(const LLSD& event) const reply[mi->first] = mi->second.mFile; } // Send the reply to the LLEventPump named in event["reply"]. - LLEventPumps::instance().obtain(event["reply"]).post(reply); + sendReply(reply, event); } void LLFloaterRegListener::showInstance(const LLSD& event) const @@ -104,6 +107,12 @@ void LLFloaterRegListener::toggleInstance(const LLSD& event) const LLFloaterReg::toggleInstance(event["name"], event["key"]); } +void LLFloaterRegListener::instanceVisible(const LLSD& event) const +{ + sendReply(LLSDMap("visible", LLFloaterReg::instanceVisible(event["name"], event["key"])), + event); +} + void LLFloaterRegListener::clickButton(const LLSD& event) const { // If the caller requests a reply, build the reply. diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h index 586656667c..24311a2dfa 100644 --- a/indra/llui/llfloaterreglistener.h +++ b/indra/llui/llfloaterreglistener.h @@ -47,6 +47,7 @@ private: void showInstance(const LLSD& event) const; void hideInstance(const LLSD& event) const; void toggleInstance(const LLSD& event) const; + void instanceVisible(const LLSD& event) const; void clickButton(const LLSD& event) const; }; diff --git a/indra/llui/llflyoutbutton.h b/indra/llui/llflyoutbutton.h index 8d59380a00..36998eba2e 100644 --- a/indra/llui/llflyoutbutton.h +++ b/indra/llui/llflyoutbutton.h @@ -46,7 +46,7 @@ public: : action_button("action_button"), allow_text_entry("allow_text_entry") { - LLComboBox::Params::allow_text_entry = false; + changeDefault(LLComboBox::Params::allow_text_entry, false); } }; diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index 43e5f6b051..724d190307 100644 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -113,6 +113,16 @@ boost::signals2::connection LLFocusableElement::setTopLostCallback(const focus_s +typedef std::list<LLHandle<LLView> > view_handle_list_t; +typedef std::map<LLHandle<LLView>, LLHandle<LLView> > focus_history_map_t; +struct LLFocusMgr::Impl +{ + // caching list of keyboard focus ancestors for calling onFocusReceived and onFocusLost + view_handle_list_t mCachedKeyboardFocusList; + + focus_history_map_t mFocusHistory; +}; + LLFocusMgr gFocusMgr; LLFocusMgr::LLFocusMgr() @@ -123,10 +133,17 @@ LLFocusMgr::LLFocusMgr() mDefaultKeyboardFocus( NULL ), mKeystrokesOnly(FALSE), mTopCtrl( NULL ), - mAppHasFocus(TRUE) // Macs don't seem to notify us that we've gotten focus, so default to true + mAppHasFocus(TRUE), // Macs don't seem to notify us that we've gotten focus, so default to true + mImpl(new LLFocusMgr::Impl) { } +LLFocusMgr::~LLFocusMgr() +{ + mImpl->mFocusHistory.clear(); + delete mImpl; + mImpl = NULL; +} void LLFocusMgr::releaseFocusIfNeeded( LLView* view ) { @@ -179,7 +196,7 @@ void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, BOOL lock, BOOL mKeyboardFocus = new_focus; // list of the focus and it's ancestors - view_handle_list_t old_focus_list = mCachedKeyboardFocusList; + view_handle_list_t old_focus_list = mImpl->mCachedKeyboardFocusList; view_handle_list_t new_focus_list; // walk up the tree to root and add all views to the new_focus_list @@ -206,7 +223,7 @@ void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, BOOL lock, BOOL LLView* old_focus_view = old_focus_iter->get(); if (old_focus_view) { - mCachedKeyboardFocusList.pop_front(); + mImpl->mCachedKeyboardFocusList.pop_front(); old_focus_view->onFocusLost(); } } @@ -219,7 +236,7 @@ void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, BOOL lock, BOOL LLView* new_focus_view = new_focus_riter->get(); if (new_focus_view) { - mCachedKeyboardFocusList.push_front(new_focus_view->getHandle()); + mImpl->mCachedKeyboardFocusList.push_front(new_focus_view->getHandle()); new_focus_view->onFocusReceived(); } } @@ -254,7 +271,7 @@ void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, BOOL lock, BOOL if (focus_subtree) { LLView* focused_view = dynamic_cast<LLView*>(mKeyboardFocus); - mFocusHistory[focus_subtree->getHandle()] = focused_view ? focused_view->getHandle() : LLHandle<LLView>(); + mImpl->mFocusHistory[focus_subtree->getHandle()] = focused_view ? focused_view->getHandle() : LLHandle<LLView>(); } } @@ -456,8 +473,8 @@ LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const { if (subtree_root) { - focus_history_map_t::const_iterator found_it = mFocusHistory.find(subtree_root->getHandle()); - if (found_it != mFocusHistory.end()) + focus_history_map_t::const_iterator found_it = mImpl->mFocusHistory.find(subtree_root->getHandle()); + if (found_it != mImpl->mFocusHistory.end()) { // found last focus for this subtree return static_cast<LLUICtrl*>(found_it->second.get()); @@ -470,6 +487,6 @@ void LLFocusMgr::clearLastFocusForGroup(LLView* subtree_root) { if (subtree_root) { - mFocusHistory.erase(subtree_root->getHandle()); + mImpl->mFocusHistory.erase(subtree_root->getHandle()); } } diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index 22c1895075..25ae1d2579 100644 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -74,7 +74,7 @@ class LLFocusMgr { public: LLFocusMgr(); - ~LLFocusMgr() { mFocusHistory.clear(); } + ~LLFocusMgr(); // Mouse Captor void setMouseCapture(LLMouseHandler* new_captor); // new_captor = NULL to release the mouse. @@ -120,6 +120,8 @@ public: bool keyboardFocusHasAccelerators() const; + struct Impl; + private: LLUICtrl* mLockedView; @@ -132,10 +134,6 @@ private: LLFocusableElement* mDefaultKeyboardFocus; BOOL mKeystrokesOnly; - // caching list of keyboard focus ancestors for calling onFocusReceived and onFocusLost - typedef std::list<LLHandle<LLView> > view_handle_list_t; - view_handle_list_t mCachedKeyboardFocusList; - // Top View LLUICtrl* mTopCtrl; @@ -143,8 +141,7 @@ private: BOOL mAppHasFocus; - typedef std::map<LLHandle<LLView>, LLHandle<LLView> > focus_history_map_t; - focus_history_map_t mFocusHistory; + Impl * mImpl; }; extern LLFocusMgr gFocusMgr; diff --git a/indra/llui/llfunctorregistry.h b/indra/llui/llfunctorregistry.h index 752c7df7ee..899cc3a326 100644 --- a/indra/llui/llfunctorregistry.h +++ b/indra/llui/llfunctorregistry.h @@ -103,7 +103,7 @@ public: } else { - llwarns << "tried to find '" << name << "' in LLFunctorRegistry, but it wasn't there." << llendl; + lldebugs << "tried to find '" << name << "' in LLFunctorRegistry, but it wasn't there." << llendl; return mMap[LOGFUNCTOR]; } } @@ -115,7 +115,7 @@ private: static void log_functor(const LLSD& notification, const LLSD& payload) { - llwarns << "log_functor called with payload: " << payload << llendl; + lldebugs << "log_functor called with payload: " << payload << llendl; } static void do_nothing(const LLSD& notification, const LLSD& payload) diff --git a/indra/llui/llhandle.h b/indra/llui/llhandle.h index a43f095d67..37c657dd92 100644 --- a/indra/llui/llhandle.h +++ b/indra/llui/llhandle.h @@ -28,17 +28,18 @@ #define LLHANDLE_H #include "llpointer.h" +#include <boost/type_traits/is_convertible.hpp> +#include <boost/utility/enable_if.hpp> -template <typename T> class LLTombStone : public LLRefCount { public: - LLTombStone(T* target = NULL) : mTarget(target) {} + LLTombStone(void* target = NULL) : mTarget(target) {} - void setTarget(T* target) { mTarget = target; } - T* getTarget() const { return mTarget; } + void setTarget(void* target) { mTarget = target; } + void* getTarget() const { return mTarget; } private: - T* mTarget; + mutable void* mTarget; }; // LLHandles are used to refer to objects whose lifetime you do not control or influence. @@ -53,20 +54,15 @@ private: template <typename T> class LLHandle { + template <typename U> friend class LLHandle; + template <typename U> friend class LLHandleProvider; public: LLHandle() : mTombStone(getDefaultTombStone()) {} - const LLHandle<T>& operator =(const LLHandle<T>& other) - { - mTombStone = other.mTombStone; - return *this; - } - template<typename Subclass> - LLHandle<T>& operator =(const LLHandle<Subclass>& other) - { - mTombStone = other.mTombStone; - return *this; - } + template<typename U> + LLHandle(const LLHandle<U>& other, typename boost::enable_if< typename boost::is_convertible<U*, T*> >::type* dummy = 0) + : mTombStone(other.mTombStone) + {} bool isDead() const { @@ -80,7 +76,7 @@ public: T* get() const { - return mTombStone->getTarget(); + return reinterpret_cast<T*>(mTombStone->getTarget()); } friend bool operator== (const LLHandle<T>& lhs, const LLHandle<T>& rhs) @@ -99,15 +95,15 @@ public: { return lhs.mTombStone > rhs.mTombStone; } -protected: protected: - LLPointer<LLTombStone<T> > mTombStone; + LLPointer<LLTombStone> mTombStone; private: - static LLPointer<LLTombStone<T> >& getDefaultTombStone() + typedef T* pointer_t; + static LLPointer<LLTombStone>& getDefaultTombStone() { - static LLPointer<LLTombStone<T> > sDefaultTombStone = new LLTombStone<T>; + static LLPointer<LLTombStone> sDefaultTombStone = new LLTombStone; return sDefaultTombStone; } }; @@ -116,23 +112,26 @@ template <typename T> class LLRootHandle : public LLHandle<T> { public: + typedef LLRootHandle<T> self_t; + typedef LLHandle<T> base_t; + LLRootHandle(T* object) { bind(object); } LLRootHandle() {}; ~LLRootHandle() { unbind(); } - // this is redundant, since a LLRootHandle *is* an LLHandle - LLHandle<T> getHandle() { return LLHandle<T>(*this); } + // this is redundant, since an LLRootHandle *is* an LLHandle + //LLHandle<T> getHandle() { return LLHandle<T>(*this); } void bind(T* object) { // unbind existing tombstone if (LLHandle<T>::mTombStone.notNull()) { - if (LLHandle<T>::mTombStone->getTarget() == object) return; + if (LLHandle<T>::mTombStone->getTarget() == (void*)object) return; LLHandle<T>::mTombStone->setTarget(NULL); } // tombstone reference counted, so no paired delete - LLHandle<T>::mTombStone = new LLTombStone<T>(object); + LLHandle<T>::mTombStone = new LLTombStone((void*)object); } void unbind() @@ -150,6 +149,15 @@ private: template <typename T> class LLHandleProvider { +public: + LLHandle<T> getHandle() const + { + // perform lazy binding to avoid small tombstone allocations for handle + // providers whose handles are never referenced + mHandle.bind(static_cast<T*>(const_cast<LLHandleProvider<T>* >(this))); + return mHandle; + } + protected: typedef LLHandle<T> handle_type_t; LLHandleProvider() @@ -157,16 +165,17 @@ protected: // provided here to enforce T deriving from LLHandleProvider<T> } - LLHandle<T> getHandle() - { - // perform lazy binding to avoid small tombstone allocations for handle - // providers whose handles are never referenced - mHandle.bind(static_cast<T*>(this)); - return mHandle; + template <typename U> + LLHandle<U> getDerivedHandle(typename boost::enable_if< typename boost::is_convertible<U*, T*> >::type* dummy = 0) const + { + LLHandle<U> downcast_handle; + downcast_handle.mTombStone = getHandle().mTombStone; + return downcast_handle; } + private: - LLRootHandle<T> mHandle; + mutable LLRootHandle<T> mHandle; }; #endif diff --git a/indra/llui/llhelp.h b/indra/llui/llhelp.h index 83317bd03c..1726347a78 100644 --- a/indra/llui/llhelp.h +++ b/indra/llui/llhelp.h @@ -32,6 +32,7 @@ class LLHelp { public: virtual void showTopic(const std::string &topic) = 0; + virtual std::string getURL(const std::string &topic) = 0; // return default (fallback) topic name suitable for showTopic() virtual std::string defaultTopic() = 0; // return topic to use before the user logs in diff --git a/indra/llui/lliconctrl.cpp b/indra/llui/lliconctrl.cpp index 627957061d..30b79b4d20 100644 --- a/indra/llui/lliconctrl.cpp +++ b/indra/llui/lliconctrl.cpp @@ -41,16 +41,15 @@ static LLDefaultChildRegistry::Register<LLIconCtrl> r("icon"); LLIconCtrl::Params::Params() : image("image_name"), color("color"), + use_draw_context_alpha("use_draw_context_alpha", true), scale_image("scale_image") -{ - tab_stop = false; - mouse_opaque = false; -} +{} LLIconCtrl::LLIconCtrl(const LLIconCtrl::Params& p) : LLUICtrl(p), mColor(p.color()), mImagep(p.image), + mUseDrawContextAlpha(p.use_draw_context_alpha), mPriority(0), mDrawWidth(0), mDrawHeight(0) @@ -71,7 +70,8 @@ void LLIconCtrl::draw() { if( mImagep.notNull() ) { - mImagep->draw(getLocalRect(), mColor.get() % getDrawContext().mAlpha ); + const F32 alpha = mUseDrawContextAlpha ? getDrawContext().mAlpha : getCurrentTransparency(); + mImagep->draw(getLocalRect(), mColor.get() % alpha ); } LLUICtrl::draw(); diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h index 79a8b0fb28..efa0925a4a 100644 --- a/indra/llui/lliconctrl.h +++ b/indra/llui/lliconctrl.h @@ -30,6 +30,7 @@ #include "lluuid.h" #include "v4color.h" #include "lluictrl.h" +#include "lluiimage.h" #include "stdenums.h" class LLTextBox; @@ -48,6 +49,7 @@ public: { Optional<LLUIImage*> image; Optional<LLUIColor> color; + Optional<bool> use_draw_context_alpha; Ignored scale_image; Params(); }; @@ -68,6 +70,7 @@ public: void setColor(const LLColor4& color) { mColor = color; } void setImage(LLPointer<LLUIImage> image) { mImagep = image; } + const LLPointer<LLUIImage> getImage() { return mImagep; } private: void setIconImageDrawSize() ; @@ -79,6 +82,10 @@ protected: S32 mDrawWidth ; S32 mDrawHeight ; + // If set to true (default), use the draw context transparency. + // If false, will use transparency returned by getCurrentTransparency(). See STORM-698. + bool mUseDrawContextAlpha; + private: LLUIColor mColor; LLPointer<LLUIImage> mImagep; diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp index ceec9c7eb1..c1cd04186b 100644 --- a/indra/llui/llkeywords.cpp +++ b/indra/llui/llkeywords.cpp @@ -57,6 +57,22 @@ LLKeywords::LLKeywords() : mLoaded(FALSE) { } +inline BOOL LLKeywordToken::isTail(const llwchar* s) const +{ + BOOL res = TRUE; + const llwchar* t = mDelimiter.c_str(); + S32 len = mDelimiter.size(); + for (S32 i=0; i<len; i++) + { + if (s[i] != t[i]) + { + res = FALSE; + break; + } + } + return res; +} + LLKeywords::~LLKeywords() { std::for_each(mWordTokenMap.begin(), mWordTokenMap.end(), DeletePairedPointer()); @@ -106,6 +122,7 @@ BOOL LLKeywords::loadFromFile( const std::string& filename ) std::string SOL_LINE("[line "); std::string SOL_ONE_SIDED_DELIMITER("[one_sided_delimiter "); std::string SOL_TWO_SIDED_DELIMITER("[two_sided_delimiter "); + std::string SOL_DOUBLE_QUOTATION_MARKS("[double_quotation_marks "); LLColor3 cur_color( 1, 0, 0 ); LLKeywordToken::TOKEN_TYPE cur_type = LLKeywordToken::WORD; @@ -137,6 +154,12 @@ BOOL LLKeywords::loadFromFile( const std::string& filename ) cur_type = LLKeywordToken::TWO_SIDED_DELIMITER; continue; } + else if( line.find(SOL_DOUBLE_QUOTATION_MARKS) == 0 ) + { + cur_color = readColor( line.substr(SOL_DOUBLE_QUOTATION_MARKS.size()) ); + cur_type = LLKeywordToken::DOUBLE_QUOTATION_MARKS; + continue; + } else if( line.find(SOL_ONE_SIDED_DELIMITER) == 0 ) { cur_color = readColor( line.substr(SOL_ONE_SIDED_DELIMITER.size()) ); @@ -154,10 +177,26 @@ BOOL LLKeywords::loadFromFile( const std::string& filename ) if( !token_buffer.empty() && token_word_iter != word_tokens.end() ) { - // first word is keyword + // first word is the keyword or a left delimiter std::string keyword = (*token_word_iter); LLStringUtil::trim(keyword); + // second word may be a right delimiter + std::string delimiter; + if (cur_type == LLKeywordToken::TWO_SIDED_DELIMITER) + { + while (delimiter.length() == 0 && ++token_word_iter != word_tokens.end()) + { + delimiter = *token_word_iter; + LLStringUtil::trim(delimiter); + } + } + else if (cur_type == LLKeywordToken::DOUBLE_QUOTATION_MARKS) + { + // Closing delimiter is identical to the opening one. + delimiter = keyword; + } + // following words are tooltip std::string tool_tip; while (++token_word_iter != word_tokens.end()) @@ -170,11 +209,11 @@ BOOL LLKeywords::loadFromFile( const std::string& filename ) { // Replace : with \n for multi-line tool tips. LLStringUtil::replaceChar( tool_tip, ':', '\n' ); - addToken(cur_type, keyword, cur_color, tool_tip ); + addToken(cur_type, keyword, cur_color, tool_tip, delimiter ); } else { - addToken(cur_type, keyword, cur_color, LLStringUtil::null ); + addToken(cur_type, keyword, cur_color, LLStringUtil::null, delimiter ); } } } @@ -189,23 +228,26 @@ BOOL LLKeywords::loadFromFile( const std::string& filename ) void LLKeywords::addToken(LLKeywordToken::TOKEN_TYPE type, const std::string& key_in, const LLColor3& color, - const std::string& tool_tip_in ) + const std::string& tool_tip_in, + const std::string& delimiter_in) { LLWString key = utf8str_to_wstring(key_in); LLWString tool_tip = utf8str_to_wstring(tool_tip_in); + LLWString delimiter = utf8str_to_wstring(delimiter_in); switch(type) { case LLKeywordToken::WORD: - mWordTokenMap[key] = new LLKeywordToken(type, color, key, tool_tip); + mWordTokenMap[key] = new LLKeywordToken(type, color, key, tool_tip, LLWStringUtil::null); break; case LLKeywordToken::LINE: - mLineTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip)); + mLineTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip, LLWStringUtil::null)); break; case LLKeywordToken::TWO_SIDED_DELIMITER: + case LLKeywordToken::DOUBLE_QUOTATION_MARKS: case LLKeywordToken::ONE_SIDED_DELIMITER: - mDelimiterTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip)); + mDelimiterTokenList.push_front(new LLKeywordToken(type, color, key, tool_tip, delimiter)); break; default: @@ -357,7 +399,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW } // cur is now at the first non-whitespace character of a new line - + // Line start tokens { BOOL line_done = FALSE; @@ -418,14 +460,15 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW S32 seg_end = 0; seg_start = cur - base; - cur += cur_delimiter->getLength(); + cur += cur_delimiter->getLengthHead(); - if( cur_delimiter->getType() == LLKeywordToken::TWO_SIDED_DELIMITER ) + LLKeywordToken::TOKEN_TYPE type = cur_delimiter->getType(); + if( type == LLKeywordToken::TWO_SIDED_DELIMITER || type == LLKeywordToken::DOUBLE_QUOTATION_MARKS ) { - while( *cur && !cur_delimiter->isHead(cur)) + while( *cur && !cur_delimiter->isTail(cur)) { // Check for an escape sequence. - if (*cur == '\\') + if (type == LLKeywordToken::DOUBLE_QUOTATION_MARKS && *cur == '\\') { // Count the number of backslashes. S32 num_backslashes = 0; @@ -435,10 +478,10 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW between_delimiters++; cur++; } - // Is the next character the end delimiter? - if (cur_delimiter->isHead(cur)) + // If the next character is the end delimiter? + if (cur_delimiter->isTail(cur)) { - // Is there was an odd number of backslashes, then this delimiter + // If there was an odd number of backslashes, then this delimiter // does not end the sequence. if (num_backslashes % 2 == 1) { @@ -461,13 +504,13 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW if( *cur ) { - cur += cur_delimiter->getLength(); - seg_end = seg_start + between_delimiters + 2 * cur_delimiter->getLength(); + cur += cur_delimiter->getLengthHead(); + seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead() + cur_delimiter->getLengthTail(); } else { // eof - seg_end = seg_start + between_delimiters + cur_delimiter->getLength(); + seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead(); } } else @@ -479,7 +522,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW between_delimiters++; cur++; } - seg_end = seg_start + between_delimiters + cur_delimiter->getLength(); + seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead(); } insertSegments(wtext, *seg_list,cur_delimiter, text_len, seg_start, seg_end, defaultColor, editor); diff --git a/indra/llui/llkeywords.h b/indra/llui/llkeywords.h index f6d75b7e75..ac34015393 100644 --- a/indra/llui/llkeywords.h +++ b/indra/llui/llkeywords.h @@ -41,23 +41,44 @@ typedef LLPointer<LLTextSegment> LLTextSegmentPtr; class LLKeywordToken { public: - enum TOKEN_TYPE { WORD, LINE, TWO_SIDED_DELIMITER, ONE_SIDED_DELIMITER }; + /** + * @brief Types of tokens/delimters being parsed. + * + * @desc Tokens/delimiters that need to be identified/highlighted. All are terminated if an EOF is encountered. + * - WORD are keywords in the normal sense, i.e. constants, events, etc. + * - LINE are for entire lines (currently only flow control labels use this). + * - ONE_SIDED_DELIMITER are for open-ended delimiters which are terminated by EOL. + * - TWO_SIDED_DELIMITER are for delimiters that end with a different delimiter than they open with. + * - DOUBLE_QUOTATION_MARKS are for delimiting areas using the same delimiter to open and close. + */ + enum TOKEN_TYPE + { + WORD, + LINE, + TWO_SIDED_DELIMITER, + ONE_SIDED_DELIMITER, + DOUBLE_QUOTATION_MARKS + }; - LLKeywordToken( TOKEN_TYPE type, const LLColor3& color, const LLWString& token, const LLWString& tool_tip ) + LLKeywordToken( TOKEN_TYPE type, const LLColor3& color, const LLWString& token, const LLWString& tool_tip, const LLWString& delimiter ) : mType( type ), mToken( token ), mColor( color ), - mToolTip( tool_tip ) + mToolTip( tool_tip ), + mDelimiter( delimiter ) // right delimiter { } - S32 getLength() const { return mToken.size(); } + S32 getLengthHead() const { return mToken.size(); } + S32 getLengthTail() const { return mDelimiter.size(); } BOOL isHead(const llwchar* s) const; + BOOL isTail(const llwchar* s) const; const LLWString& getToken() const { return mToken; } const LLColor3& getColor() const { return mColor; } TOKEN_TYPE getType() const { return mType; } const LLWString& getToolTip() const { return mToolTip; } + const LLWString& getDelimiter() const { return mDelimiter; } #ifdef _DEBUG void dump(); @@ -68,6 +89,7 @@ private: LLWString mToken; LLColor3 mColor; LLWString mToolTip; + LLWString mDelimiter; }; class LLKeywords @@ -85,7 +107,8 @@ public: void addToken(LLKeywordToken::TOKEN_TYPE type, const std::string& key, const LLColor3& color, - const std::string& tool_tip = LLStringUtil::null); + const std::string& tool_tip = LLStringUtil::null, + const std::string& delimiter = LLStringUtil::null); // This class is here as a performance optimization. // The word token map used to be defined as std::map<LLWString, LLKeywordToken*>. diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp index 940c7e7e18..4c730286da 100644 --- a/indra/llui/lllayoutstack.cpp +++ b/indra/llui/lllayoutstack.cpp @@ -34,60 +34,175 @@ #include "llpanel.h" #include "llresizebar.h" #include "llcriticaldamp.h" +#include "boost/foreach.hpp" + +static const F32 MIN_FRACTIONAL_SIZE = 0.0f; +static const F32 MAX_FRACTIONAL_SIZE = 1.f; static LLDefaultChildRegistry::Register<LLLayoutStack> register_layout_stack("layout_stack"); static LLLayoutStack::LayoutStackRegistry::Register<LLLayoutPanel> register_layout_panel("layout_panel"); +void LLLayoutStack::OrientationNames::declareValues() +{ + declare("horizontal", HORIZONTAL); + declare("vertical", VERTICAL); +} + // // LLLayoutPanel // +LLLayoutPanel::Params::Params() +: expanded_min_dim("expanded_min_dim", 0), + min_dim("min_dim", -1), + user_resize("user_resize", false), + auto_resize("auto_resize", true) +{ + addSynonym(min_dim, "min_width"); + addSynonym(min_dim, "min_height"); +} + LLLayoutPanel::LLLayoutPanel(const Params& p) : LLPanel(p), + mExpandedMinDim(p.expanded_min_dim.isProvided() ? p.expanded_min_dim : p.min_dim), mMinDim(p.min_dim), - mMaxDim(p.max_dim), mAutoResize(p.auto_resize), mUserResize(p.user_resize), - mCollapsed(FALSE), - mCollapseAmt(0.f), - mVisibleAmt(1.f), // default to fully visible - mResizeBar(NULL) - { + mCollapsed(FALSE), + mCollapseAmt(0.f), + mVisibleAmt(1.f), // default to fully visible + mResizeBar(NULL), + mFractionalSize(MIN_FRACTIONAL_SIZE), + mTargetDim(0), + mIgnoreReshape(false), + mOrientation(LLLayoutStack::HORIZONTAL) +{ // panels initialized as hidden should not start out partially visible if (!getVisible()) - { + { mVisibleAmt = 0.f; - } - } + } +} void LLLayoutPanel::initFromParams(const Params& p) - { +{ LLPanel::initFromParams(p); setFollowsNone(); - } +} LLLayoutPanel::~LLLayoutPanel() +{ + // probably not necessary, but... + delete mResizeBar; + mResizeBar = NULL; +} + +F32 LLLayoutPanel::getAutoResizeFactor() const +{ + return mVisibleAmt * (1.f - mCollapseAmt); +} + +F32 LLLayoutPanel::getVisibleAmount() const +{ + return mVisibleAmt; +} + +S32 LLLayoutPanel::getLayoutDim() const +{ + return llround((F32)((mOrientation == LLLayoutStack::HORIZONTAL) + ? getRect().getWidth() + : getRect().getHeight())); +} + +S32 LLLayoutPanel::getTargetDim() const +{ + return mTargetDim; +} + +void LLLayoutPanel::setTargetDim(S32 value) +{ + LLRect new_rect(getRect()); + if (mOrientation == LLLayoutStack::HORIZONTAL) { - // probably not necessary, but... - delete mResizeBar; - mResizeBar = NULL; + new_rect.mRight = new_rect.mLeft + value; } - -F32 LLLayoutPanel::getCollapseFactor(LLLayoutStack::ELayoutOrientation orientation) + else { - if (orientation == LLLayoutStack::HORIZONTAL) + new_rect.mTop = new_rect.mBottom + value; + } + setShape(new_rect, true); +} + +S32 LLLayoutPanel::getVisibleDim() const +{ + F32 min_dim = getRelevantMinDim(); + return llround(mVisibleAmt + * (min_dim + + (((F32)mTargetDim - min_dim) * (1.f - mCollapseAmt)))); +} + +void LLLayoutPanel::setOrientation( LLLayoutStack::ELayoutOrientation orientation ) +{ + mOrientation = orientation; + S32 layout_dim = llround((F32)((mOrientation == LLLayoutStack::HORIZONTAL) + ? getRect().getWidth() + : getRect().getHeight())); + + if (mAutoResize == FALSE + && mUserResize == TRUE + && mMinDim == -1 ) + { + setMinDim(layout_dim); + } + mTargetDim = llmax(layout_dim, getMinDim()); +} + +void LLLayoutPanel::setVisible( BOOL visible ) +{ + if (visible != getVisible()) + { + LLLayoutStack* stackp = dynamic_cast<LLLayoutStack*>(getParent()); + if (stackp) { - F32 collapse_amt = - clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinDim / (F32)llmax(1, getRect().getWidth())); - return mVisibleAmt * collapse_amt; + stackp->mNeedsLayout = true; } - else + } + LLPanel::setVisible(visible); +} + +void LLLayoutPanel::reshape( S32 width, S32 height, BOOL called_from_parent /*= TRUE*/ ) +{ + if (width == getRect().getWidth() && height == getRect().getHeight()) return; + + if (!mIgnoreReshape && mAutoResize == false) { - F32 collapse_amt = - clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, llmin(1.f, (F32)mMinDim / (F32)llmax(1, getRect().getHeight()))); - return mVisibleAmt * collapse_amt; + mTargetDim = (mOrientation == LLLayoutStack::HORIZONTAL) ? width : height; + LLLayoutStack* stackp = dynamic_cast<LLLayoutStack*>(getParent()); + if (stackp) + { + stackp->mNeedsLayout = true; } } + LLPanel::reshape(width, height, called_from_parent); +} + +void LLLayoutPanel::handleReshape(const LLRect& new_rect, bool by_user) +{ + LLLayoutStack* stackp = dynamic_cast<LLLayoutStack*>(getParent()); + if (stackp) + { + if (by_user) + { // tell layout stack to account for new shape + + // make sure that panels have already been auto resized + stackp->updateLayout(); + // now apply requested size to panel + stackp->updatePanelRect(this, new_rect); + } + stackp->mNeedsLayout = true; + } + LLPanel::handleReshape(new_rect, by_user); +} // // LLLayoutStack @@ -97,20 +212,23 @@ LLLayoutStack::Params::Params() : orientation("orientation"), animate("animate", true), clip("clip", true), + open_time_constant("open_time_constant", 0.02f), + close_time_constant("close_time_constant", 0.03f), + resize_bar_overlap("resize_bar_overlap", 1), border_size("border_size", LLCachedControl<S32>(*LLUI::sSettingGroups["config"], "UIResizeBarHeight", 0)) -{ - name="stack"; -} +{} LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p) : LLView(p), - mMinWidth(0), - mMinHeight(0), mPanelSpacing(p.border_size), - mOrientation((p.orientation() == "vertical") ? VERTICAL : HORIZONTAL), + mOrientation(p.orientation), mAnimate(p.animate), mAnimatedThisFrame(false), - mClip(p.clip) + mNeedsLayout(true), + mClip(p.clip), + mOpenTimeConstant(p.open_time_constant), + mCloseTimeConstant(p.close_time_constant), + mResizeBarOverlap(p.resize_bar_overlap) {} LLLayoutStack::~LLLayoutStack() @@ -124,28 +242,27 @@ void LLLayoutStack::draw() { updateLayout(); - e_panel_list_t::iterator panel_it; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + // always clip to stack itself + LLLocalClipRect clip(getLocalRect()); + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) { // clip to layout rectangle, not bounding rectangle - LLRect clip_rect = (*panel_it)->getRect(); + LLRect clip_rect = panelp->getRect(); // scale clipping rectangle by visible amount if (mOrientation == HORIZONTAL) { - clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->getCollapseFactor(mOrientation)); + clip_rect.mRight = clip_rect.mLeft + panelp->getVisibleDim(); } else { - clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->getCollapseFactor(mOrientation)); + clip_rect.mBottom = clip_rect.mTop - panelp->getVisibleDim(); } - LLPanel* panelp = (*panel_it); - - LLLocalClipRect clip(clip_rect, mClip); - // only force drawing invisible children if visible amount is non-zero - drawChild(panelp, 0, 0, !clip_rect.isEmpty()); + {LLLocalClipRect clip(clip_rect, mClip); + // only force drawing invisible children if visible amount is non-zero + drawChild(panelp, 0, 0, !clip_rect.isEmpty()); + } } - mAnimatedThisFrame = false; } void LLLayoutStack::removeChild(LLView* view) @@ -156,12 +273,10 @@ void LLLayoutStack::removeChild(LLView* view) { mPanels.erase(std::find(mPanels.begin(), mPanels.end(), embedded_panelp)); delete embedded_panelp; + updateFractionalSizes(); + mNeedsLayout = true; } - // need to update resizebars - - calcMinExtents(); - LLView::removeChild(view); } @@ -172,54 +287,19 @@ BOOL LLLayoutStack::postBuild() } bool LLLayoutStack::addChild(LLView* child, S32 tab_group) - { - LLLayoutPanel* panelp = dynamic_cast<LLLayoutPanel*>(child); - if (panelp) - { - mPanels.push_back(panelp); - } - return LLView::addChild(child, tab_group); - } - - -S32 LLLayoutStack::getDefaultHeight(S32 cur_height) -{ - // if we are spanning our children (crude upward propagation of size) - // then don't enforce our size on our children - if (mOrientation == HORIZONTAL) - { - cur_height = llmax(mMinHeight, getRect().getHeight()); - } - - return cur_height; -} - -S32 LLLayoutStack::getDefaultWidth(S32 cur_width) { - // if we are spanning our children (crude upward propagation of size) - // then don't enforce our size on our children - if (mOrientation == VERTICAL) + LLLayoutPanel* panelp = dynamic_cast<LLLayoutPanel*>(child); + if (panelp) { - cur_width = llmax(mMinWidth, getRect().getWidth()); + panelp->setOrientation(mOrientation); + mPanels.push_back(panelp); + createResizeBar(panelp); + mNeedsLayout = true; } + BOOL result = LLView::addChild(child, tab_group); - return cur_width; -} - -void LLLayoutStack::movePanel(LLPanel* panel_to_move, LLPanel* target_panel, bool move_to_front) -{ - LLLayoutPanel* embedded_panel_to_move = findEmbeddedPanel(panel_to_move); - LLLayoutPanel* embedded_target_panel = move_to_front ? *mPanels.begin() : findEmbeddedPanel(target_panel); - - if (!embedded_panel_to_move || !embedded_target_panel || embedded_panel_to_move == embedded_target_panel) - { - llwarns << "One of the panels was not found in stack or NULL was passed instead of valid panel" << llendl; - return; - } - e_panel_list_t::iterator it = std::find(mPanels.begin(), mPanels.end(), embedded_panel_to_move); - mPanels.erase(it); - it = move_to_front ? mPanels.begin() : std::find(mPanels.begin(), mPanels.end(), embedded_target_panel); - mPanels.insert(it, embedded_panel_to_move); + updateFractionalSizes(); + return result; } void LLLayoutStack::addPanel(LLLayoutPanel* panel, EAnimate animate) @@ -234,483 +314,544 @@ void LLLayoutStack::addPanel(LLLayoutPanel* panel, EAnimate animate) } } -void LLLayoutStack::removePanel(LLPanel* panel) -{ - removeChild(panel); -} - void LLLayoutStack::collapsePanel(LLPanel* panel, BOOL collapsed) { LLLayoutPanel* panel_container = findEmbeddedPanel(panel); if (!panel_container) return; panel_container->mCollapsed = collapsed; + mNeedsLayout = true; } -void LLLayoutStack::updatePanelAutoResize(const std::string& panel_name, BOOL auto_resize) -{ - LLLayoutPanel* panel = findEmbeddedPanelByName(panel_name); - - if (panel) - { - panel->mAutoResize = auto_resize; - } -} - -void LLLayoutStack::setPanelUserResize(const std::string& panel_name, BOOL user_resize) -{ - LLLayoutPanel* panel = findEmbeddedPanelByName(panel_name); - - if (panel) - { - panel->mUserResize = user_resize; - } -} - -bool LLLayoutStack::getPanelMinSize(const std::string& panel_name, S32* min_dimp) -{ - LLLayoutPanel* panel = findEmbeddedPanelByName(panel_name); +static LLFastTimer::DeclareTimer FTM_UPDATE_LAYOUT("Update LayoutStacks"); - if (panel) - { - if (min_dimp) *min_dimp = panel->mMinDim; - } +void LLLayoutStack::updateLayout() +{ + LLFastTimer ft(FTM_UPDATE_LAYOUT); - return NULL != panel; -} + if (!mNeedsLayout) return; -bool LLLayoutStack::getPanelMaxSize(const std::string& panel_name, S32* max_dimp) -{ - LLLayoutPanel* panel = findEmbeddedPanelByName(panel_name); + bool continue_animating = animatePanels(); + F32 total_visible_fraction = 0.f; + S32 space_to_distribute = (mOrientation == HORIZONTAL) + ? getRect().getWidth() + : getRect().getHeight(); - if (panel) + // first, assign minimum dimensions + LLLayoutPanel* panelp = NULL; + BOOST_FOREACH(panelp, mPanels) { - if (max_dimp) *max_dimp = panel->mMaxDim; + if (panelp->mAutoResize) + { + panelp->mTargetDim = panelp->getRelevantMinDim(); + } + space_to_distribute -= panelp->getVisibleDim() + llround((F32)mPanelSpacing * panelp->getVisibleAmount()); + total_visible_fraction += panelp->mFractionalSize * panelp->getAutoResizeFactor(); } - return NULL != panel; -} + llassert(total_visible_fraction < 1.05f); -static LLFastTimer::DeclareTimer FTM_UPDATE_LAYOUT("Update LayoutStacks"); -void LLLayoutStack::updateLayout(BOOL force_resize) -{ - LLFastTimer ft(FTM_UPDATE_LAYOUT); - static LLUICachedControl<S32> resize_bar_overlap ("UIResizeBarOverlap", 0); - calcMinExtents(); - createResizeBars(); + // don't need spacing after last panel + space_to_distribute += panelp ? llround((F32)mPanelSpacing * panelp->getVisibleAmount()) : 0; - // calculate current extents - S32 total_width = 0; - S32 total_height = 0; - - const F32 ANIM_OPEN_TIME = 0.02f; - const F32 ANIM_CLOSE_TIME = 0.03f; - - e_panel_list_t::iterator panel_it; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) - { - LLPanel* panelp = (*panel_it); - if (panelp->getVisible()) + S32 remaining_space = space_to_distribute; + F32 fraction_distributed = 0.f; + if (space_to_distribute > 0 && total_visible_fraction > 0.f) + { // give space proportionally to visible auto resize panels + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) { - if (mAnimate) - { - if (!mAnimatedThisFrame) - { - (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_OPEN_TIME)); - if ((*panel_it)->mVisibleAmt > 0.99f) - { - (*panel_it)->mVisibleAmt = 1.f; - } - } - } - else + if (panelp->mAutoResize) { - (*panel_it)->mVisibleAmt = 1.f; + F32 fraction_to_distribute = (panelp->mFractionalSize * panelp->getAutoResizeFactor()) / (total_visible_fraction); + S32 delta = llround((F32)space_to_distribute * fraction_to_distribute); + fraction_distributed += fraction_to_distribute; + panelp->mTargetDim += delta; + remaining_space -= delta; } } - else // not visible + } + + // distribute any left over pixels to non-collapsed, visible panels + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) + { + if (remaining_space == 0) break; + + if (panelp->mAutoResize + && !panelp->mCollapsed + && panelp->getVisible()) { - if (mAnimate) - { - if (!mAnimatedThisFrame) - { - (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); - if ((*panel_it)->mVisibleAmt < 0.001f) - { - (*panel_it)->mVisibleAmt = 0.f; - } - } - } - else - { - (*panel_it)->mVisibleAmt = 0.f; - } + S32 space_for_panel = remaining_space > 0 ? 1 : -1; + panelp->mTargetDim += space_for_panel; + remaining_space -= space_for_panel; } + } + + F32 cur_pos = (mOrientation == HORIZONTAL) ? 0.f : (F32)getRect().getHeight(); + + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) + { + F32 panel_dim = llmax(panelp->getExpandedMinDim(), panelp->mTargetDim); + F32 panel_visible_dim = panelp->getVisibleDim(); - if ((*panel_it)->mCollapsed) + LLRect panel_rect; + if (mOrientation == HORIZONTAL) { - (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + panel_rect.setLeftTopAndSize(llround(cur_pos), + getRect().getHeight(), + llround(panel_dim), + getRect().getHeight()); } else { - (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + panel_rect.setLeftTopAndSize(0, + llround(cur_pos), + getRect().getWidth(), + llround(panel_dim)); } + panelp->setIgnoreReshape(true); + panelp->setShape(panel_rect); + panelp->setIgnoreReshape(false); + + LLRect resize_bar_rect(panel_rect); + F32 panel_spacing = (F32)mPanelSpacing * panelp->getVisibleAmount(); if (mOrientation == HORIZONTAL) { - // enforce minimize size constraint by default - if (panelp->getRect().getWidth() < (*panel_it)->mMinDim) - { - panelp->reshape((*panel_it)->mMinDim, panelp->getRect().getHeight()); - } - total_width += llround(panelp->getRect().getWidth() * (*panel_it)->getCollapseFactor(mOrientation)); - // want n-1 panel gaps for n panels - if (panel_it != mPanels.begin()) - { - total_width += mPanelSpacing; - } + resize_bar_rect.mLeft = panel_rect.mRight - mResizeBarOverlap; + resize_bar_rect.mRight = panel_rect.mRight + (S32)(llround(panel_spacing)) + mResizeBarOverlap; + + cur_pos += panel_visible_dim + panel_spacing; } else //VERTICAL { - // enforce minimize size constraint by default - if (panelp->getRect().getHeight() < (*panel_it)->mMinDim) - { - panelp->reshape(panelp->getRect().getWidth(), (*panel_it)->mMinDim); - } - total_height += llround(panelp->getRect().getHeight() * (*panel_it)->getCollapseFactor(mOrientation)); - if (panel_it != mPanels.begin()) - { - total_height += mPanelSpacing; - } + resize_bar_rect.mTop = panel_rect.mBottom + mResizeBarOverlap; + resize_bar_rect.mBottom = panel_rect.mBottom - (S32)(llround(panel_spacing)) - mResizeBarOverlap; + + cur_pos -= panel_visible_dim + panel_spacing; } + panelp->mResizeBar->setShape(resize_bar_rect); } - S32 num_resizable_panels = 0; - S32 shrink_headroom_available = 0; - S32 shrink_headroom_total = 0; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + updateResizeBarLimits(); + + // clear animation flag at end, since panel resizes will set it + // and leave it set if there is any animation in progress + mNeedsLayout = continue_animating; +} // end LLLayoutStack::updateLayout + +LLLayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const +{ + if (!panelp) return NULL; + + e_panel_list_t::const_iterator panel_it; + BOOST_FOREACH(LLLayoutPanel* p, mPanels) { - // panels that are not fully visible do not count towards shrink headroom - if ((*panel_it)->getCollapseFactor(mOrientation) < 1.f) + if (p == panelp) { - continue; + return p; } + } + return NULL; +} + +LLLayoutPanel* LLLayoutStack::findEmbeddedPanelByName(const std::string& name) const +{ + LLLayoutPanel* result = NULL; - // if currently resizing a panel or the panel is flagged as not automatically resizing - // only track total available headroom, but don't use it for automatic resize logic - if ((*panel_it)->mResizeBar->hasMouseCapture() - || (!(*panel_it)->mAutoResize - && !force_resize)) + BOOST_FOREACH(LLLayoutPanel* p, mPanels) + { + if (p->getName() == name) { - if (mOrientation == HORIZONTAL) - { - shrink_headroom_total += (*panel_it)->getRect().getWidth() - (*panel_it)->mMinDim; - } - else //VERTICAL - { - shrink_headroom_total += (*panel_it)->getRect().getHeight() - (*panel_it)->mMinDim; - } + result = p; + break; } - else + } + + return result; +} + +void LLLayoutStack::createResizeBar(LLLayoutPanel* panelp) +{ + BOOST_FOREACH(LLLayoutPanel* lp, mPanels) + { + if (lp->mResizeBar == NULL) { - num_resizable_panels++; - if (mOrientation == HORIZONTAL) - { - shrink_headroom_available += (*panel_it)->getRect().getWidth() - (*panel_it)->mMinDim; - shrink_headroom_total += (*panel_it)->getRect().getWidth() - (*panel_it)->mMinDim; - } - else //VERTICAL - { - shrink_headroom_available += (*panel_it)->getRect().getHeight() - (*panel_it)->mMinDim; - shrink_headroom_total += (*panel_it)->getRect().getHeight() - (*panel_it)->mMinDim; - } + LLResizeBar::Side side = (mOrientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM; + LLRect resize_bar_rect = getRect(); + + LLResizeBar::Params resize_params; + resize_params.name("resize"); + resize_params.resizing_view(lp); + resize_params.min_size(lp->getRelevantMinDim()); + resize_params.side(side); + resize_params.snapping_enabled(false); + LLResizeBar* resize_bar = LLUICtrlFactory::create<LLResizeBar>(resize_params); + lp->mResizeBar = resize_bar; + LLView::addChild(resize_bar, 0); } } - - // calculate how many pixels need to be distributed among layout panels - // positive means panels need to grow, negative means shrink - S32 pixels_to_distribute; - if (mOrientation == HORIZONTAL) + // bring all resize bars to the front so that they are clickable even over the panels + // with a bit of overlap + for (e_panel_list_t::iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) { - pixels_to_distribute = getRect().getWidth() - total_width; + LLResizeBar* resize_barp = (*panel_it)->mResizeBar; + sendChildToFront(resize_barp); } - else //VERTICAL +} + +// update layout stack animations, etc. once per frame +// NOTE: we use this to size world view based on animating UI, *before* we draw the UI +// we might still need to call updateLayout during UI draw phase, in case UI elements +// are resizing themselves dynamically +//static +void LLLayoutStack::updateClass() +{ + for (instance_iter it = beginInstances(); it != endInstances(); ++it) { - pixels_to_distribute = getRect().getHeight() - total_height; + it->updateLayout(); + it->mAnimatedThisFrame = false; } +} + +void LLLayoutStack::updateFractionalSizes() +{ + F32 total_resizable_dim = 0.f; - // now we distribute the pixels... - S32 cur_x = 0; - S32 cur_y = getRect().getHeight(); + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) + { + if (panelp->mAutoResize) + { + total_resizable_dim += llmax(0, panelp->getLayoutDim() - panelp->getRelevantMinDim()); + } + } - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) { - LLPanel* panelp = (*panel_it); + if (panelp->mAutoResize) + { + F32 panel_resizable_dim = llmax(MIN_FRACTIONAL_SIZE, (F32)(panelp->getLayoutDim() - panelp->getRelevantMinDim())); + panelp->mFractionalSize = panel_resizable_dim > 0.f + ? llclamp(panel_resizable_dim / total_resizable_dim, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE) + : MIN_FRACTIONAL_SIZE; + llassert(!llisnan(panelp->mFractionalSize)); + } + } - S32 cur_width = panelp->getRect().getWidth(); - S32 cur_height = panelp->getRect().getHeight(); - S32 new_width = cur_width; - S32 new_height = cur_height; + normalizeFractionalSizes(); +} - if (mOrientation == HORIZONTAL) + +void LLLayoutStack::normalizeFractionalSizes() +{ + S32 num_auto_resize_panels = 0; + F32 total_fractional_size = 0.f; + + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) + { + if (panelp->mAutoResize) { - new_width = llmax((*panel_it)->mMinDim, new_width); + total_fractional_size += panelp->mFractionalSize; + num_auto_resize_panels++; } - else + } + + if (total_fractional_size == 0.f) + { // equal distribution + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) + { + if (panelp->mAutoResize) + { + panelp->mFractionalSize = MAX_FRACTIONAL_SIZE / (F32)num_auto_resize_panels; + } + } + } + else + { // renormalize + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) { - new_height = llmax((*panel_it)->mMinDim, new_height); + if (panelp->mAutoResize) + { + panelp->mFractionalSize /= total_fractional_size; + } } - S32 delta_size = 0; + } +} - // if panel can automatically resize (not animating, and resize flag set)... - if ((*panel_it)->getCollapseFactor(mOrientation) == 1.f - && (force_resize || (*panel_it)->mAutoResize) - && !(*panel_it)->mResizeBar->hasMouseCapture()) +bool LLLayoutStack::animatePanels() +{ + bool continue_animating = false; + + // + // animate visibility + // + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) + { + if (panelp->getVisible()) { - if (mOrientation == HORIZONTAL) + if (mAnimate && panelp->mVisibleAmt < 1.f) { - // if we're shrinking - if (pixels_to_distribute < 0) - { - // shrink proportionally to amount over minimum - // so we can do this in one pass - delta_size = (shrink_headroom_available > 0) ? llround((F32)pixels_to_distribute * ((F32)(cur_width - (*panel_it)->mMinDim) / (F32)shrink_headroom_available)) : 0; - shrink_headroom_available -= (cur_width - (*panel_it)->mMinDim); - } - else + if (!mAnimatedThisFrame) { - // grow all elements equally - delta_size = llround((F32)pixels_to_distribute / (F32)num_resizable_panels); - num_resizable_panels--; + panelp->mVisibleAmt = lerp(panelp->mVisibleAmt, 1.f, LLCriticalDamp::getInterpolant(mOpenTimeConstant)); + if (panelp->mVisibleAmt > 0.99f) + { + panelp->mVisibleAmt = 1.f; + } } - pixels_to_distribute -= delta_size; - new_width = llmax((*panel_it)->mMinDim, cur_width + delta_size); + + mAnimatedThisFrame = true; + continue_animating = true; } else { - new_width = getDefaultWidth(new_width); - } - - if (mOrientation == VERTICAL) - { - if (pixels_to_distribute < 0) + if (panelp->mVisibleAmt != 1.f) { - // shrink proportionally to amount over minimum - // so we can do this in one pass - delta_size = (shrink_headroom_available > 0) ? llround((F32)pixels_to_distribute * ((F32)(cur_height - (*panel_it)->mMinDim) / (F32)shrink_headroom_available)) : 0; - shrink_headroom_available -= (cur_height - (*panel_it)->mMinDim); + panelp->mVisibleAmt = 1.f; + mAnimatedThisFrame = true; } - else + } + } + else // not visible + { + if (mAnimate && panelp->mVisibleAmt > 0.f) + { + if (!mAnimatedThisFrame) { - delta_size = llround((F32)pixels_to_distribute / (F32)num_resizable_panels); - num_resizable_panels--; + panelp->mVisibleAmt = lerp(panelp->mVisibleAmt, 0.f, LLCriticalDamp::getInterpolant(mCloseTimeConstant)); + if (panelp->mVisibleAmt < 0.001f) + { + panelp->mVisibleAmt = 0.f; + } } - pixels_to_distribute -= delta_size; - new_height = llmax((*panel_it)->mMinDim, cur_height + delta_size); + + continue_animating = true; + mAnimatedThisFrame = true; } else { - new_height = getDefaultHeight(new_height); + if (panelp->mVisibleAmt != 0.f) + { + panelp->mVisibleAmt = 0.f; + mAnimatedThisFrame = true; + } } } - else + + F32 collapse_state = panelp->mCollapsed ? 1.f : 0.f; + if (panelp->mCollapseAmt != collapse_state) { - if (mOrientation == HORIZONTAL) + if (mAnimate) { - new_height = getDefaultHeight(new_height); + if (!mAnimatedThisFrame) + { + panelp->mCollapseAmt = lerp(panelp->mCollapseAmt, collapse_state, LLCriticalDamp::getInterpolant(mCloseTimeConstant)); + } + + if (llabs(panelp->mCollapseAmt - collapse_state) < 0.001f) + { + panelp->mCollapseAmt = collapse_state; + } + + mAnimatedThisFrame = true; + continue_animating = true; } - else // VERTICAL + else { - new_width = getDefaultWidth(new_width); + panelp->mCollapseAmt = collapse_state; + mAnimatedThisFrame = true; } } + } - // adjust running headroom count based on new sizes - shrink_headroom_total += delta_size; + if (mAnimatedThisFrame) mNeedsLayout = true; + return continue_animating; +} - LLRect panel_rect; - panel_rect.setLeftTopAndSize(cur_x, cur_y, new_width, new_height); - panelp->setShape(panel_rect); +void LLLayoutStack::updatePanelRect( LLLayoutPanel* resized_panel, const LLRect& new_rect ) +{ + S32 new_dim = (mOrientation == HORIZONTAL) + ? new_rect.getWidth() + : new_rect.getHeight(); + S32 delta_dim = new_dim - resized_panel->getVisibleDim(); + if (delta_dim == 0) return; - LLRect resize_bar_rect = panel_rect; - if (mOrientation == HORIZONTAL) - { - resize_bar_rect.mLeft = panel_rect.mRight - resize_bar_overlap; - resize_bar_rect.mRight = panel_rect.mRight + mPanelSpacing + resize_bar_overlap; - } - else - { - resize_bar_rect.mTop = panel_rect.mBottom + resize_bar_overlap; - resize_bar_rect.mBottom = panel_rect.mBottom - mPanelSpacing - resize_bar_overlap; - } - (*panel_it)->mResizeBar->setRect(resize_bar_rect); + F32 total_visible_fraction = 0.f; + F32 delta_auto_resize_headroom = 0.f; + F32 original_auto_resize_headroom = 0.f; - if (mOrientation == HORIZONTAL) - { - cur_x += llround(new_width * (*panel_it)->getCollapseFactor(mOrientation)) + mPanelSpacing; - } - else //VERTICAL - { - cur_y -= llround(new_height * (*panel_it)->getCollapseFactor(mOrientation)) + mPanelSpacing; - } - } + LLLayoutPanel* other_resize_panel = NULL; + LLLayoutPanel* following_panel = NULL; - // update resize bars with new limits - LLResizeBar* last_resize_bar = NULL; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + BOOST_REVERSE_FOREACH(LLLayoutPanel* panelp, mPanels) { - LLPanel* panelp = (*panel_it); - - if (mOrientation == HORIZONTAL) + if (panelp->mAutoResize) { - (*panel_it)->mResizeBar->setResizeLimits( - (*panel_it)->mMinDim, - (*panel_it)->mMinDim + shrink_headroom_total); + original_auto_resize_headroom += (F32)(panelp->mTargetDim - panelp->getRelevantMinDim()); + if (panelp->getVisible() && !panelp->mCollapsed) + { + total_visible_fraction += panelp->mFractionalSize; + } } - else //VERTICAL + + if (panelp == resized_panel) { - (*panel_it)->mResizeBar->setResizeLimits( - (*panel_it)->mMinDim, - (*panel_it)->mMinDim + shrink_headroom_total); + other_resize_panel = following_panel; } - // toggle resize bars based on panel visibility, resizability, etc - BOOL resize_bar_enabled = panelp->getVisible() && (*panel_it)->mUserResize; - (*panel_it)->mResizeBar->setVisible(resize_bar_enabled); - - if (resize_bar_enabled) + if (panelp->getVisible() && !panelp->mCollapsed) { - last_resize_bar = (*panel_it)->mResizeBar; + following_panel = panelp; } } - // hide last resize bar as there is nothing past it - // resize bars need to be in between two resizable panels - if (last_resize_bar) - { - last_resize_bar->setVisible(FALSE); - } - // not enough room to fit existing contents - if (force_resize == FALSE - // layout did not complete by reaching target position - && ((mOrientation == VERTICAL && cur_y != -mPanelSpacing) - || (mOrientation == HORIZONTAL && cur_x != getRect().getWidth() + mPanelSpacing))) + if (resized_panel->mAutoResize) { - // do another layout pass with all stacked elements contributing - // even those that don't usually resize - llassert_always(force_resize == FALSE); - updateLayout(TRUE); + if (!other_resize_panel || !other_resize_panel->mAutoResize) + { + delta_auto_resize_headroom += delta_dim; + } } - - mAnimatedThisFrame = true; -} // end LLLayoutStack::updateLayout - - -LLLayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const -{ - if (!panelp) return NULL; - - e_panel_list_t::const_iterator panel_it; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + else { - if ((*panel_it) == panelp) + if (!other_resize_panel || other_resize_panel->mAutoResize) { - return *panel_it; + delta_auto_resize_headroom -= delta_dim; } } - return NULL; -} -LLLayoutPanel* LLLayoutStack::findEmbeddedPanelByName(const std::string& name) const -{ - LLLayoutPanel* result = NULL; + F32 fraction_given_up = 0.f; + F32 fraction_remaining = 1.f; + F32 updated_auto_resize_headroom = original_auto_resize_headroom + delta_auto_resize_headroom; - for (e_panel_list_t::const_iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + enum { - LLLayoutPanel* p = *panel_it; + BEFORE_RESIZED_PANEL, + RESIZED_PANEL, + NEXT_PANEL, + AFTER_RESIZED_PANEL + } which_panel = BEFORE_RESIZED_PANEL; - if (p->getName() == name) + BOOST_FOREACH(LLLayoutPanel* panelp, mPanels) + { + if (!panelp->getVisible() || panelp->mCollapsed) continue; + + if (panelp == resized_panel) { - result = p; - break; + which_panel = RESIZED_PANEL; } - } - - return result; -} -// Compute sum of min_width or min_height of children -void LLLayoutStack::calcMinExtents() -{ - mMinWidth = 0; - mMinHeight = 0; - - e_panel_list_t::iterator panel_it; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) - { - if (mOrientation == HORIZONTAL) + switch(which_panel) { - mMinWidth += (*panel_it)->mMinDim; - if (panel_it != mPanels.begin()) + case BEFORE_RESIZED_PANEL: + if (panelp->mAutoResize) + { // freeze current size as fraction of overall auto_resize space + F32 fractional_adjustment_factor = updated_auto_resize_headroom == 0.f + ? 1.f + : original_auto_resize_headroom / updated_auto_resize_headroom; + F32 new_fractional_size = llclamp(panelp->mFractionalSize * fractional_adjustment_factor, + MIN_FRACTIONAL_SIZE, + MAX_FRACTIONAL_SIZE); + fraction_given_up -= new_fractional_size - panelp->mFractionalSize; + fraction_remaining -= panelp->mFractionalSize; + panelp->mFractionalSize = new_fractional_size; + llassert(!llisnan(panelp->mFractionalSize)); + } + else { - mMinWidth += mPanelSpacing; + // leave non auto-resize panels alone } - } - else //VERTICAL - { - mMinHeight += (*panel_it)->mMinDim; - if (panel_it != mPanels.begin()) + break; + case RESIZED_PANEL: + if (panelp->mAutoResize) + { // freeze new size as fraction + F32 new_fractional_size = (updated_auto_resize_headroom == 0.f) + ? MAX_FRACTIONAL_SIZE + : llclamp(total_visible_fraction * (F32)(new_dim - panelp->getRelevantMinDim()) / updated_auto_resize_headroom, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE); + fraction_given_up -= new_fractional_size - panelp->mFractionalSize; + fraction_remaining -= panelp->mFractionalSize; + panelp->mFractionalSize = new_fractional_size; + llassert(!llisnan(panelp->mFractionalSize)); + } + else + { // freeze new size as original size + panelp->mTargetDim = new_dim; + } + which_panel = NEXT_PANEL; + break; + case NEXT_PANEL: + if (panelp->mAutoResize) { - mMinHeight += mPanelSpacing; + fraction_remaining -= panelp->mFractionalSize; + if (resized_panel->mAutoResize) + { + panelp->mFractionalSize = llclamp(panelp->mFractionalSize + fraction_given_up, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE); + fraction_given_up = 0.f; + } + else + { + F32 new_fractional_size = llclamp(total_visible_fraction * (F32)(panelp->mTargetDim - panelp->getRelevantMinDim() + delta_auto_resize_headroom) + / updated_auto_resize_headroom, + MIN_FRACTIONAL_SIZE, + MAX_FRACTIONAL_SIZE); + fraction_given_up -= new_fractional_size - panelp->mFractionalSize; + panelp->mFractionalSize = new_fractional_size; + } } + else + { + panelp->mTargetDim -= delta_dim; + } + which_panel = AFTER_RESIZED_PANEL; + break; + case AFTER_RESIZED_PANEL: + if (panelp->mAutoResize && fraction_given_up != 0.f) + { + panelp->mFractionalSize = llclamp(panelp->mFractionalSize + (panelp->mFractionalSize / fraction_remaining) * fraction_given_up, + MIN_FRACTIONAL_SIZE, + MAX_FRACTIONAL_SIZE); + } + default: + break; } } + updateLayout(); + normalizeFractionalSizes(); } -void LLLayoutStack::createResizeBars() +void LLLayoutStack::reshape(S32 width, S32 height, BOOL called_from_parent) { - for (e_panel_list_t::iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + mNeedsLayout = true; + LLView::reshape(width, height, called_from_parent); +} + +void LLLayoutStack::updateResizeBarLimits() +{ + LLLayoutPanel* previous_visible_panelp = NULL; + BOOST_REVERSE_FOREACH(LLLayoutPanel* visible_panelp, mPanels) { - LLLayoutPanel* lp = (*panel_it); - if (lp->mResizeBar == NULL) + if (!visible_panelp->getVisible() || visible_panelp->mCollapsed) { - LLResizeBar::Side side = (mOrientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM; - LLRect resize_bar_rect = getRect(); - - LLResizeBar::Params resize_params; - resize_params.name("resize"); - resize_params.resizing_view(lp); - resize_params.min_size(lp->mMinDim); - resize_params.side(side); - resize_params.snapping_enabled(false); - LLResizeBar* resize_bar = LLUICtrlFactory::create<LLResizeBar>(resize_params); - lp->mResizeBar = resize_bar; - LLView::addChild(resize_bar, 0); + visible_panelp->mResizeBar->setVisible(FALSE); + continue; + } - // bring all resize bars to the front so that they are clickable even over the panels - // with a bit of overlap - for (e_panel_list_t::iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) - { - LLResizeBar* resize_barp = (*panel_it)->mResizeBar; - sendChildToFront(resize_barp); - } + // toggle resize bars based on panel visibility, resizability, etc + if (previous_visible_panelp + && (visible_panelp->mUserResize || previous_visible_panelp->mUserResize) // one of the pair is user resizable + && (visible_panelp->mAutoResize || visible_panelp->mUserResize) // current panel is resizable + && (previous_visible_panelp->mAutoResize || previous_visible_panelp->mUserResize)) // previous panel is resizable + { + visible_panelp->mResizeBar->setVisible(TRUE); + S32 previous_panel_headroom = previous_visible_panelp->getVisibleDim() - previous_visible_panelp->getRelevantMinDim(); + visible_panelp->mResizeBar->setResizeLimits(visible_panelp->getRelevantMinDim(), + visible_panelp->getVisibleDim() + previous_panel_headroom); + } + else + { + visible_panelp->mResizeBar->setVisible(FALSE); } - } -} -// update layout stack animations, etc. once per frame -// NOTE: we use this to size world view based on animating UI, *before* we draw the UI -// we might still need to call updateLayout during UI draw phase, in case UI elements -// are resizing themselves dynamically -//static -void LLLayoutStack::updateClass() -{ - LLInstanceTrackerScopedGuard guard; - for (LLLayoutStack::instance_iter it = guard.beginInstances(); - it != guard.endInstances(); - ++it) - { - it->updateLayout(); + previous_visible_panelp = visible_panelp; } } diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h index e19ef403ef..648cd5fdce 100644 --- a/indra/llui/lllayoutstack.h +++ b/indra/llui/lllayoutstack.h @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010, Linden Reshasearch, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -30,46 +30,54 @@ #include "llpanel.h" -class LLPanel; class LLLayoutPanel; + class LLLayoutStack : public LLView, public LLInstanceTracker<LLLayoutStack> { public: + typedef enum e_layout_orientation + { + HORIZONTAL, + VERTICAL + } ELayoutOrientation; + + struct OrientationNames + : public LLInitParam::TypeValuesHelper<ELayoutOrientation, OrientationNames> + { + static void declareValues(); + }; + struct LayoutStackRegistry : public LLChildRegistry<LayoutStackRegistry> {}; struct Params : public LLInitParam::Block<Params, LLView::Params> { - Mandatory<std::string> orientation; + Mandatory<ELayoutOrientation, OrientationNames> orientation; Optional<S32> border_size; Optional<bool> animate, clip; + Optional<F32> open_time_constant, + close_time_constant; + Optional<S32> resize_bar_overlap; Params(); }; typedef LayoutStackRegistry child_registry_t; - typedef enum e_layout_orientation - { - HORIZONTAL, - VERTICAL - } ELayoutOrientation; - virtual ~LLLayoutStack(); /*virtual*/ void draw(); /*virtual*/ void removeChild(LLView*); /*virtual*/ BOOL postBuild(); - /*virtual*/ bool addChild(LLView* child, S32 tab_group = 0); + /*virtual*/ bool addChild(LLView* child, S32 tab_groupdatefractuiona = 0); + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); - S32 getMinWidth() const { return mMinWidth; } - S32 getMinHeight() const { return mMinHeight; } - typedef enum e_animate { NO_ANIMATE, @@ -77,49 +85,24 @@ public: } EAnimate; void addPanel(LLLayoutPanel* panel, EAnimate animate = NO_ANIMATE); - void removePanel(LLPanel* panel); void collapsePanel(LLPanel* panel, BOOL collapsed = TRUE); S32 getNumPanels() { return mPanels.size(); } - /** - * Moves panel_to_move before target_panel inside layout stack (both panels should already be there). - * If move_to_front is true target_panel is ignored and panel_to_move is moved to the beginning of mPanels - */ - void movePanel(LLPanel* panel_to_move, LLPanel* target_panel, bool move_to_front = false); - - void updatePanelAutoResize(const std::string& panel_name, BOOL auto_resize); - void setPanelUserResize(const std::string& panel_name, BOOL user_resize); - - /** - * Gets minimal dimension along layout_stack axis of the specified by name panel. - * - * @returns true if specified by panel_name internal panel exists, false otherwise. - */ - bool getPanelMinSize(const std::string& panel_name, S32* min_dimp); - - /** - * Gets maximal dimension along layout_stack axis of the specified by name panel. - * - * @returns true if specified by panel_name internal panel exists, false otherwise. - */ - bool getPanelMaxSize(const std::string& panel_name, S32* max_dim); - - void updateLayout(BOOL force_resize = FALSE); - + + void updateLayout(); + S32 getPanelSpacing() const { return mPanelSpacing; } - BOOL getAnimate () const { return mAnimate; } - void setAnimate (BOOL animate) { mAnimate = animate; } static void updateClass(); protected: LLLayoutStack(const Params&); friend class LLUICtrlFactory; + friend class LLLayoutPanel; private: - void createResizeBars(); - void calcMinExtents(); - S32 getDefaultHeight(S32 cur_height); - S32 getDefaultWidth(S32 cur_width); + void updateResizeBarLimits(); + bool animatePanels(); + void createResizeBar(LLLayoutPanel* panel); const ELayoutOrientation mOrientation; @@ -128,17 +111,23 @@ private: LLLayoutPanel* findEmbeddedPanel(LLPanel* panelp) const; LLLayoutPanel* findEmbeddedPanelByName(const std::string& name) const; + void updateFractionalSizes(); + void normalizeFractionalSizes(); + void updatePanelRect( LLLayoutPanel* param1, const LLRect& new_rect ); - S32 mMinWidth; // calculated by calcMinExtents - S32 mMinHeight; // calculated by calcMinExtents S32 mPanelSpacing; // true if we already applied animation this frame bool mAnimatedThisFrame; bool mAnimate; bool mClip; + F32 mOpenTimeConstant; + F32 mCloseTimeConstant; + bool mNeedsLayout; + S32 mResizeBarOverlap; }; // end class LLLayoutStack + class LLLayoutPanel : public LLPanel { friend class LLLayoutStack; @@ -146,41 +135,71 @@ friend class LLUICtrlFactory; public: struct Params : public LLInitParam::Block<Params, LLPanel::Params> { - Optional<S32> min_dim, - max_dim; + Optional<S32> expanded_min_dim, + min_dim; Optional<bool> user_resize, auto_resize; - Params() - : min_dim("min_dim", 0), - max_dim("max_dim", 0), - user_resize("user_resize", true), - auto_resize("auto_resize", true) - { - addSynonym(min_dim, "min_width"); - addSynonym(min_dim, "min_height"); - addSynonym(max_dim, "max_width"); - addSynonym(max_dim, "max_height"); - } + Params(); }; ~LLLayoutPanel(); void initFromParams(const Params& p); -protected: - LLLayoutPanel(const Params& p) ; + void handleReshape(const LLRect& new_rect, bool by_user); + + void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + + void setVisible(BOOL visible); + + S32 getLayoutDim() const; + S32 getTargetDim() const; + void setTargetDim(S32 value); + S32 getMinDim() const { return llmax(0, mMinDim); } + void setMinDim(S32 value) { mMinDim = value; } + + S32 getExpandedMinDim() const { return mExpandedMinDim >= 0 ? mExpandedMinDim : getMinDim(); } + void setExpandedMinDim(S32 value) { mExpandedMinDim = value; } - F32 getCollapseFactor(LLLayoutStack::ELayoutOrientation orientation); + S32 getRelevantMinDim() const + { + S32 min_dim = mMinDim; + + if (!mCollapsed) + { + min_dim = getExpandedMinDim(); + } + + return min_dim; + } - S32 mMinDim; - S32 mMaxDim; - BOOL mAutoResize; - BOOL mUserResize; - BOOL mCollapsed; + F32 getAutoResizeFactor() const; + F32 getVisibleAmount() const; + S32 getVisibleDim() const; + + void setOrientation(LLLayoutStack::ELayoutOrientation orientation); + void storeOriginalDim(); + + void setIgnoreReshape(bool ignore) { mIgnoreReshape = ignore; } + +protected: + LLLayoutPanel(const Params& p); + + const bool mAutoResize; + const bool mUserResize; + + S32 mExpandedMinDim; + S32 mMinDim; + bool mCollapsed; + F32 mVisibleAmt; + F32 mCollapseAmt; + F32 mFractionalSize; + S32 mTargetDim; + bool mIgnoreReshape; + LLLayoutStack::ELayoutOrientation mOrientation; class LLResizeBar* mResizeBar; - F32 mVisibleAmt; - F32 mCollapseAmt; }; diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 3eb58e1aec..d0fbf4b913 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -37,6 +37,7 @@ #include "llgl.h" #include "lltimer.h" +#include "llcalc.h" //#include "llclipboard.h" #include "llcontrol.h" #include "llbutton.h" @@ -81,6 +82,7 @@ LLLineEditor::Params::Params() : max_length(""), keystroke_callback("keystroke_callback"), prevalidate_callback("prevalidate_callback"), + prevalidate_input_callback("prevalidate_input_callback"), background_image("background_image"), background_image_disabled("background_image_disabled"), background_image_focused("background_image_focused"), @@ -88,6 +90,7 @@ LLLineEditor::Params::Params() revert_on_esc("revert_on_esc", true), commit_on_focus_lost("commit_on_focus_lost", true), ignore_tab("ignore_tab", true), + is_password("is_password", false), cursor_color("cursor_color"), text_color("text_color"), text_readonly_color("text_readonly_color"), @@ -100,10 +103,11 @@ LLLineEditor::Params::Params() text_pad_right("text_pad_right"), default_text("default_text") { - mouse_opaque = true; + changeDefault(mouse_opaque, true); addSynonym(select_on_focus, "select_all_on_focus_received"); addSynonym(border, "border"); addSynonym(label, "watermark_text"); + addSynonym(max_length.chars, "max_length"); } LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) @@ -129,8 +133,9 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mBorderThickness( 0 ), mIgnoreArrowKeys( FALSE ), mIgnoreTab( p.ignore_tab ), - mDrawAsterixes( FALSE ), + mDrawAsterixes( p.is_password ), mSelectAllonFocusReceived( p.select_on_focus ), + mSelectAllonCommit( TRUE ), mPassDelete(FALSE), mReadOnly(FALSE), mBgImage( p.background_image ), @@ -172,6 +177,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) updateTextPadding(); setCursor(mText.length()); + setPrevalidateInput(p.prevalidate_input_callback()); setPrevalidate(p.prevalidate_callback()); LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile<LLContextMenu> @@ -192,6 +198,7 @@ LLLineEditor::~LLLineEditor() void LLLineEditor::onFocusReceived() { + gEditMenuHandler = this; LLUICtrl::onFocusReceived(); updateAllowingLanguageInput(); } @@ -227,7 +234,10 @@ void LLLineEditor::onCommit() setControlValue(getValue()); LLUICtrl::onCommit(); - selectAll(); + + // Selection on commit needs to be turned off when evaluating maths + // expressions, to allow indication of the error position + if (mSelectAllonCommit) selectAll(); } // Returns TRUE if user changed value at all @@ -388,7 +398,11 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) setCursor(llmin((S32)mText.length(), getCursor())); // Set current history line to end of history. - if(mLineHistory.end() != mLineHistory.begin()) + if (mLineHistory.empty()) + { + mCurrentHistoryLine = mLineHistory.end(); + } + else { mCurrentHistoryLine = mLineHistory.end() - 1; } @@ -400,23 +414,15 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) // Picks a new cursor position based on the actual screen size of text being drawn. void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) { - const llwchar* wtext = mText.getWString().c_str(); - LLWString asterix_text; - if (mDrawAsterixes) - { - for (S32 i = 0; i < mText.length(); i++) - { - asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK); - } - wtext = asterix_text.c_str(); - } + S32 cursor_pos = calcCursorPos(local_mouse_x); + + S32 left_pos = llmin( mSelectionStart, cursor_pos ); + S32 length = llabs( mSelectionStart - cursor_pos ); + const LLWString& substr = mText.getWString().substr(left_pos, length); + + if (mIsSelecting && !prevalidateInput(substr)) + return; - S32 cursor_pos = - mScrollHPos + - mGLFont->charFromPixelOffset( - wtext, mScrollHPos, - (F32)(local_mouse_x - mTextLeftEdge), - (F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive setCursor(cursor_pos); } @@ -500,6 +506,11 @@ BOOL LLLineEditor::canSelectAll() const void LLLineEditor::selectAll() { + if (!prevalidateInput(mText.getWString())) + { + return; + } + mSelectionStart = mText.length(); mSelectionEnd = 0; setCursor(mSelectionEnd); @@ -585,6 +596,9 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) if (mask & MASK_SHIFT) { + // assume we're starting a drag select + mIsSelecting = TRUE; + // Handle selection extension S32 old_cursor_pos = getCursor(); setCursorAtLocalPos(x); @@ -619,8 +633,6 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) mSelectionStart = old_cursor_pos; mSelectionEnd = getCursor(); } - // assume we're starting a drag select - mIsSelecting = TRUE; } else { @@ -791,6 +803,9 @@ void LLLineEditor::removeChar() { if( getCursor() > 0 ) { + if (!prevalidateInput(mText.getWString().substr(getCursor()-1, 1))) + return; + mText.erase(getCursor() - 1, 1); setCursor(getCursor() - 1); @@ -811,6 +826,9 @@ void LLLineEditor::addChar(const llwchar uni_char) } else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) { + if (!prevalidateInput(mText.getWString().substr(getCursor(), 1))) + return; + mText.erase(getCursor(), 1); } @@ -859,6 +877,13 @@ void LLLineEditor::extendSelection( S32 new_cursor_pos ) startSelection(); } + S32 left_pos = llmin( mSelectionStart, new_cursor_pos ); + S32 selection_length = llabs( mSelectionStart - new_cursor_pos ); + const LLWString& selection = mText.getWString().substr(left_pos, selection_length); + + if (!prevalidateInput(selection)) + return; + setCursor(new_cursor_pos); mSelectionEnd = getCursor(); } @@ -989,8 +1014,12 @@ void LLLineEditor::deleteSelection() { if( !mReadOnly && hasSelection() ) { - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 selection_length = llabs( mSelectionStart - mSelectionEnd ); + S32 left_pos, selection_length; + getSelectionRange(&left_pos, &selection_length); + const LLWString& selection = mText.getWString().substr(left_pos, selection_length); + + if (!prevalidateInput(selection)) + return; mText.erase(left_pos, selection_length); deselect(); @@ -1008,13 +1037,17 @@ void LLLineEditor::cut() { if( canCut() ) { + S32 left_pos, length; + getSelectionRange(&left_pos, &length); + const LLWString& selection = mText.getWString().substr(left_pos, length); + + if (!prevalidateInput(selection)) + return; + // Prepare for possible rollback LLLineEditorRollback rollback( this ); - - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromSubstring( mText.getWString(), left_pos, length ); + LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length ); deleteSelection(); // Validate new string and rollback the if needed. @@ -1045,13 +1078,13 @@ void LLLineEditor::copy() { S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromSubstring( mText.getWString(), left_pos, length ); + LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length ); } } BOOL LLLineEditor::canPaste() const { - return !mReadOnly && gClipboard.canPasteString(); + return !mReadOnly && LLClipboard::instance().isTextAvailable(); } void LLLineEditor::paste() @@ -1082,17 +1115,13 @@ void LLLineEditor::pasteHelper(bool is_primary) if (can_paste_it) { LLWString paste; - if (is_primary) - { - paste = gClipboard.getPastePrimaryWString(); - } - else - { - paste = gClipboard.getPasteWString(); - } + LLClipboard::instance().pasteFromClipboard(paste, is_primary); if (!paste.empty()) { + if (!prevalidateInput(paste)) + return; + // Prepare for possible rollback LLLineEditorRollback rollback(this); @@ -1173,13 +1202,13 @@ void LLLineEditor::copyPrimary() { S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromPrimarySubstring( mText.getWString(), left_pos, length ); + LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length, true); } } BOOL LLLineEditor::canPastePrimary() const { - return !mReadOnly && gClipboard.canPastePrimaryString(); + return !mReadOnly && LLClipboard::instance().isTextAvailable(true); } void LLLineEditor::updatePrimary() @@ -1440,6 +1469,13 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char) LLLineEditorRollback rollback( this ); + { + LLWString u_char; + u_char.assign(1, uni_char); + if (!prevalidateInput(u_char)) + return handled; + } + addChar(uni_char); mKeystrokeTimer.reset(); @@ -1491,6 +1527,15 @@ void LLLineEditor::doDelete() } else if ( getCursor() < mText.length()) { + const LLWString& text_to_delete = mText.getWString().substr(getCursor(), 1); + + if (!prevalidateInput(text_to_delete)) + { + if( mKeystrokeCallback ) + mKeystrokeCallback( this ); + + return; + } setCursor(getCursor() + 1); removeChar(); } @@ -1529,8 +1574,11 @@ void LLLineEditor::drawBackground() { image = mBgImage; } + + if (!image) return; - F32 alpha = getDrawContext().mAlpha; + F32 alpha = getCurrentTransparency(); + // optionally draw programmatic border if (has_focus) { @@ -1575,7 +1623,7 @@ void LLLineEditor::draw() LLRect background( 0, getRect().getHeight(), getRect().getWidth(), 0 ); background.stretch( -mBorderThickness ); - S32 lineeditor_v_pad = llround((background.getHeight() - mGLFont->getLineHeight())/2); + S32 lineeditor_v_pad = (background.getHeight() - mGLFont->getLineHeight()) / 2; drawBackground(); @@ -1835,6 +1883,27 @@ S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const return result; } +S32 LLLineEditor::calcCursorPos(S32 mouse_x) +{ + const llwchar* wtext = mText.getWString().c_str(); + LLWString asterix_text; + if (mDrawAsterixes) + { + for (S32 i = 0; i < mText.length(); i++) + { + asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK); + } + wtext = asterix_text.c_str(); + } + + S32 cur_pos = mScrollHPos + + mGLFont->charFromPixelOffset( + wtext, mScrollHPos, + (F32)(mouse_x - mTextLeftEdge), + (F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive + + return cur_pos; +} //virtual void LLLineEditor::clear() { @@ -1928,6 +1997,22 @@ void LLLineEditor::setPrevalidate(LLTextValidate::validate_func_t func) updateAllowingLanguageInput(); } +void LLLineEditor::setPrevalidateInput(LLTextValidate::validate_func_t func) +{ + mPrevalidateInputFunc = func; + updateAllowingLanguageInput(); +} + +bool LLLineEditor::prevalidateInput(const LLWString& wstr) +{ + if (mPrevalidateInputFunc && !mPrevalidateInputFunc(wstr)) + { + return false; + } + + return true; +} + // static BOOL LLLineEditor::postvalidateFloat(const std::string &str) { @@ -1987,6 +2072,32 @@ BOOL LLLineEditor::postvalidateFloat(const std::string &str) return success; } +BOOL LLLineEditor::evaluateFloat() +{ + bool success; + F32 result = 0.f; + std::string expr = getText(); + LLStringUtil::toUpper(expr); + + success = LLCalc::getInstance()->evalString(expr, result); + + if (!success) + { + // Move the cursor to near the error on failure + setCursor(LLCalc::getInstance()->getLastErrorPos()); + // *TODO: Translated error message indicating the type of error? Select error text? + } + else + { + // Replace the expression with the result + std::string result_str = llformat("%f",result); + setText(result_str); + selectAll(); + } + + return success; +} + void LLLineEditor::onMouseCaptureLost() { endSelection(); @@ -2286,3 +2397,8 @@ void LLLineEditor::setContextMenu(LLContextMenu* new_context_menu) else mContextMenuHandle.markDead(); } + +void LLLineEditor::setFont(const LLFontGL* font) +{ + mGLFont = font; +} diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index a1aa6b71c6..2518dbe3c7 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -41,6 +41,7 @@ #include "lleditmenuhandler.h" #include "lluictrl.h" +#include "lluiimage.h" #include "lluistring.h" #include "llviewborder.h" @@ -59,7 +60,7 @@ public: typedef boost::function<void (LLLineEditor* caller)> keystroke_callback_t; - struct MaxLength : public LLInitParam::Choice<MaxLength> + struct MaxLength : public LLInitParam::ChoiceBlock<MaxLength> { Alternative<S32> bytes, chars; @@ -75,6 +76,7 @@ public: Optional<keystroke_callback_t> keystroke_callback; Optional<LLTextValidate::validate_func_t, LLTextValidate::ValidateTextNamedFuncs> prevalidate_callback; + Optional<LLTextValidate::validate_func_t, LLTextValidate::ValidateTextNamedFuncs> prevalidate_input_callback; Optional<LLViewBorder::Params> border; @@ -85,7 +87,8 @@ public: Optional<bool> select_on_focus, revert_on_esc, commit_on_focus_lost, - ignore_tab; + ignore_tab, + is_password; // colors Optional<LLUIColor> cursor_color, @@ -200,6 +203,7 @@ public: const LLColor4& getTentativeFgColor() const { return mTentativeFgColor.get(); } const LLFontGL* getFont() const { return mGLFont; } + void setFont(const LLFontGL* font); void setIgnoreArrowKeys(BOOL b) { mIgnoreArrowKeys = b; } void setIgnoreTab(BOOL b) { mIgnoreTab = b; } @@ -217,6 +221,7 @@ public: void deleteSelection(); void setSelectAllonFocusReceived(BOOL b); + void setSelectAllonCommit(BOOL b) { mSelectAllonCommit = b; } typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t; void setKeystrokeCallback(callback_t callback, void* user_data); @@ -229,8 +234,16 @@ public: // Prevalidation controls which keystrokes can affect the editor void setPrevalidate( LLTextValidate::validate_func_t func ); + // This method sets callback that prevents from: + // - deleting, selecting, typing, cutting, pasting characters that are not valid. + // Also callback that this method sets differs from setPrevalidate in a way that it validates just inputed + // symbols, before existing text is modified, but setPrevalidate validates line after it was modified. + void setPrevalidateInput(LLTextValidate::validate_func_t func); static BOOL postvalidateFloat(const std::string &str); + bool prevalidateInput(const LLWString& wstr); + BOOL evaluateFloat(); + // line history support: void setEnableLineHistory( BOOL enabled ) { mHaveHistory = enabled; } // switches line history on or off void updateHistory(); // stores current line in history @@ -248,6 +261,7 @@ private: void addChar(const llwchar c); void setCursorAtLocalPos(S32 local_mouse_x); S32 findPixelNearestPos(S32 cursor_offset = 0) const; + S32 calcCursorPos(S32 mouse_x); BOOL handleSpecialKey(KEY key, MASK mask); BOOL handleSelectionKey(KEY key, MASK mask); BOOL handleControlKey(KEY key, MASK mask); @@ -309,6 +323,7 @@ protected: S32 mLastSelectionEnd; LLTextValidate::validate_func_t mPrevalidateFunc; + LLTextValidate::validate_func_t mPrevalidateInputFunc; LLFrameTimer mKeystrokeTimer; LLTimer mTripleClickTimer; @@ -327,6 +342,7 @@ protected: BOOL mDrawAsterixes; BOOL mSelectAllonFocusReceived; + BOOL mSelectAllonCommit; BOOL mPassDelete; BOOL mReadOnly; @@ -336,7 +352,7 @@ protected: std::vector<S32> mPreeditPositions; LLPreeditor::standouts_t mPreeditStandouts; - LLHandle<LLView> mContextMenuHandle; + LLHandle<LLContextMenu> mContextMenuHandle; private: // Instances that by default point to the statics but can be overidden in XML. diff --git a/indra/llui/llloadingindicator.cpp b/indra/llui/llloadingindicator.cpp index 7b29d92ea0..6ac38f5ad4 100644 --- a/indra/llui/llloadingindicator.cpp +++ b/indra/llui/llloadingindicator.cpp @@ -34,61 +34,28 @@ // Project includes #include "lluictrlfactory.h" #include "lluiimage.h" +#include "boost/foreach.hpp" // registered in llui.cpp to avoid being left out by MS linker //static LLDefaultChildRegistry::Register<LLLoadingIndicator> r("loading_indicator"); /////////////////////////////////////////////////////////////////////////////// -// LLLoadingIndicator::Data class +// LLLoadingIndicator class /////////////////////////////////////////////////////////////////////////////// -/** - * Pre-loaded images shared by all instances of the widget - */ -class LLLoadingIndicator::Data: public LLSingleton<LLLoadingIndicator::Data> +LLLoadingIndicator::LLLoadingIndicator(const Params& p) +: LLUICtrl(p), + mImagesPerSec(p.images_per_sec > 0 ? p.images_per_sec : 1.0f), + mCurImageIdx(0) { -public: - /*virtual*/ void initSingleton(); // from LLSingleton - - LLPointer<LLUIImage> getNextImage(S8& idx) const; - U8 getImagesCount() const { return NIMAGES; } -private: - - static const U8 NIMAGES = 12; - LLPointer<LLUIImage> mImages[NIMAGES]; -}; +} -// virtual -// Called right after the instance gets constructed. -void LLLoadingIndicator::Data::initSingleton() +void LLLoadingIndicator::initFromParams(const Params& p) { - // Load images. - for (U8 i = 0; i < NIMAGES; ++i) + BOOST_FOREACH(LLUIImage* image, p.images.image) { - std::string img_name = llformat("Progress_%d", i+1); - mImages[i] = LLUI::getUIImage(img_name, 0); - llassert(mImages[i]); + mImages.push_back(image); } -} - -LLPointer<LLUIImage> LLLoadingIndicator::Data::getNextImage(S8& idx) const -{ - // Calculate next index, performing array bounds checking. - idx = (idx >= NIMAGES || idx < 0) ? 0 : (idx + 1) % NIMAGES; - return mImages[idx]; -} - -/////////////////////////////////////////////////////////////////////////////// -// LLLoadingIndicator class -/////////////////////////////////////////////////////////////////////////////// - -LLLoadingIndicator::LLLoadingIndicator(const Params& p) -: LLUICtrl(p) - , mRotationsPerSec(p.rotations_per_sec > 0 ? p.rotations_per_sec : 1.0f) - , mCurImageIdx(-1) -{ - // Select initial image. - mCurImagep = Data::instance().getNextImage(mCurImageIdx); // Start timer for switching images. start(); @@ -100,16 +67,21 @@ void LLLoadingIndicator::draw() if (mImageSwitchTimer.getStarted() && mImageSwitchTimer.hasExpired()) { // Switch to the next image. - mCurImagep = Data::instance().getNextImage(mCurImageIdx); + if (!mImages.empty()) + { + mCurImageIdx = (mCurImageIdx + 1) % mImages.size(); + } // Restart timer. start(); } + LLUIImagePtr cur_image = mImages.empty() ? LLUIImagePtr(NULL) : mImages[mCurImageIdx]; + // Draw current image. - if( mCurImagep.notNull() ) + if( cur_image.notNull() ) { - mCurImagep->draw(getLocalRect(), LLColor4::white % getDrawContext().mAlpha); + cur_image->draw(getLocalRect(), LLColor4::white % getDrawContext().mAlpha); } LLUICtrl::draw(); @@ -123,6 +95,6 @@ void LLLoadingIndicator::stop() void LLLoadingIndicator::start() { mImageSwitchTimer.start(); - F32 period = 1.0f / (Data::instance().getImagesCount() * mRotationsPerSec); + F32 period = 1.0f / (mImages.size() * mImagesPerSec); mImageSwitchTimer.setTimerExpirySec(period); } diff --git a/indra/llui/llloadingindicator.h b/indra/llui/llloadingindicator.h index 4e4a224ef6..c1f979c111 100644 --- a/indra/llui/llloadingindicator.h +++ b/indra/llui/llloadingindicator.h @@ -28,6 +28,7 @@ #define LL_LLLOADINGINDICATOR_H #include "lluictrl.h" +#include "lluiimage.h" /////////////////////////////////////////////////////////////////////////////// // class LLLoadingIndicator @@ -36,8 +37,8 @@ /** * Perpetual loading indicator (a la MacOSX or YouTube) * - * Number of rotations per second can be overriden - * with the "roations_per_sec" parameter. + * Number of rotations per second can be overridden + * with the "images_per_sec" parameter. * * Can start/stop spinning. * @@ -49,11 +50,24 @@ class LLLoadingIndicator { LOG_CLASS(LLLoadingIndicator); public: + + struct Images : public LLInitParam::BatchBlock<Images> + { + Multiple<LLUIImage*> image; + + Images() + : image("image") + {} + }; + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { - Optional<F32> rotations_per_sec; + Optional<F32> images_per_sec; + Optional<Images> images; + Params() - : rotations_per_sec("rotations_per_sec", 1.0f) + : images_per_sec("images_per_sec", 1.0f), + images("images") {} }; @@ -72,16 +86,19 @@ public: */ void start(); + void reset() { mCurImageIdx = 0; } + private: LLLoadingIndicator(const Params&); - friend class LLUICtrlFactory; + void initFromParams(const Params&); - class Data; + friend class LLUICtrlFactory; - F32 mRotationsPerSec; + F32 mImagesPerSec; S8 mCurImageIdx; - LLPointer<LLUIImage> mCurImagep; LLFrameTimer mImageSwitchTimer; + + std::vector<LLUIImagePtr> mImages; }; #endif // LL_LLLOADINGINDICATOR_H diff --git a/indra/llui/llmenubutton.cpp b/indra/llui/llmenubutton.cpp index ac568a83e4..50d59f79f4 100644 --- a/indra/llui/llmenubutton.cpp +++ b/indra/llui/llmenubutton.cpp @@ -35,9 +35,16 @@ static LLDefaultChildRegistry::Register<LLMenuButton> r("menu_button"); +void LLMenuButton::MenuPositions::declareValues() +{ + declare("topleft", MP_TOP_LEFT); + declare("topright", MP_TOP_RIGHT); + declare("bottomleft", MP_BOTTOM_LEFT); +} LLMenuButton::Params::Params() -: menu_filename("menu_filename") +: menu_filename("menu_filename"), + position("position", MP_BOTTOM_LEFT) { } @@ -45,7 +52,7 @@ LLMenuButton::Params::Params() LLMenuButton::LLMenuButton(const LLMenuButton::Params& p) : LLButton(p), mIsMenuShown(false), - mMenuPosition(MP_BOTTOM_LEFT) + mMenuPosition(p.position) { std::string menu_filename = p.menu_filename; @@ -175,6 +182,13 @@ void LLMenuButton::updateMenuOrigin() mY = rect.mTop + mMenuHandle.get()->getRect().getHeight(); break; } + case MP_TOP_RIGHT: + { + const LLRect& menu_rect = mMenuHandle.get()->getRect(); + mX = rect.mRight - menu_rect.getWidth(); + mY = rect.mTop + menu_rect.getHeight(); + break; + } case MP_BOTTOM_LEFT: { mX = rect.mLeft; diff --git a/indra/llui/llmenubutton.h b/indra/llui/llmenubutton.h index 9e91b9e99d..e2396e7fb2 100644 --- a/indra/llui/llmenubutton.h +++ b/indra/llui/llmenubutton.h @@ -35,20 +35,30 @@ class LLMenuButton : public LLButton { public: + typedef enum e_menu_position + { + MP_TOP_LEFT, + MP_TOP_RIGHT, + MP_BOTTOM_LEFT + } EMenuPosition; + + struct MenuPositions + : public LLInitParam::TypeValuesHelper<EMenuPosition, MenuPositions> + { + static void declareValues(); + }; + struct Params : public LLInitParam::Block<Params, LLButton::Params> { // filename for it's toggleable menu Optional<std::string> menu_filename; + Optional<EMenuPosition> position; Params(); }; - typedef enum e_menu_position - { - MP_TOP_LEFT, - MP_BOTTOM_LEFT - } EMenuPosition; + boost::signals2::connection setMouseDownCallback( const mouse_signal_t::slot_type& cb ); diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index a6cf86d9b8..ff6928ffda 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -90,7 +90,6 @@ const S32 TEAROFF_SEPARATOR_HEIGHT_PIXELS = 10; const S32 MENU_ITEM_PADDING = 4; const std::string SEPARATOR_NAME("separator"); -const std::string SEPARATOR_LABEL( "-----------" ); const std::string VERTICAL_SEPARATOR_LABEL( "|" ); const std::string LLMenuGL::BOOLEAN_TRUE_PREFIX( "\xE2\x9C\x94" ); // U+2714 HEAVY CHECK MARK @@ -149,7 +148,7 @@ LLMenuItemGL::Params::Params() highlight_bg_color("highlight_bg_color"), highlight_fg_color("highlight_fg_color") { - mouse_opaque = true; + changeDefault(mouse_opaque, true); } // Default constructor @@ -318,7 +317,7 @@ void LLMenuItemGL::setJumpKey(KEY key) // virtual U32 LLMenuItemGL::getNominalHeight( void ) const { - return llround(mFont->getLineHeight()) + MENU_ITEM_PADDING; + return mFont->getLineHeight() + MENU_ITEM_PADDING; } //virtual @@ -509,19 +508,19 @@ void LLMenuItemGL::draw( void ) { if( !mDrawBoolLabel.empty() ) { - mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE ); } - mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE ); if( !mDrawAccelLabel.empty() ) { - mFont->render( mDrawAccelLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + mFont->render( mDrawAccelLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE ); } if( !mDrawBranchLabel.empty() ) { - mFont->render( mDrawBranchLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f) + 1.f, color, + mFont->render( mDrawBranchLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE ); } } @@ -566,8 +565,6 @@ void LLMenuItemGL::handleVisibilityChange(BOOL new_visibility) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LLMenuItemSeparatorGL::Params::Params() { - name = "separator"; - label = SEPARATOR_LABEL; } LLMenuItemSeparatorGL::LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p) : @@ -755,30 +752,6 @@ U32 LLMenuItemTearOffGL::getNominalHeight( void ) const return TEAROFF_SEPARATOR_HEIGHT_PIXELS; } - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemBlankGL -// -// This class represents a blank, non-functioning item. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemBlankGL : public LLMenuItemGL -{ -public: - struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> - { - Params() - { - name=""; - enabled = false; - } - }; - LLMenuItemBlankGL( const Params& p ) : LLMenuItemGL( p ) - {} - virtual void draw( void ) {} -}; - - ///============================================================================ /// Class LLMenuItemCallGL ///============================================================================ @@ -974,9 +947,14 @@ LLMenuItemBranchGL::LLMenuItemBranchGL(const LLMenuItemBranchGL::Params& p) LLMenuItemBranchGL::~LLMenuItemBranchGL() { - LLView::deleteViewByHandle(mBranchHandle); + if (mBranchHandle.get()) + { + mBranchHandle.get()->die(); + } } + + // virtual LLView* LLMenuItemBranchGL::getChildView(const std::string& name, BOOL recurse) const { @@ -1462,7 +1440,7 @@ BOOL LLMenuItemBranchDownGL::handleAcceleratorKey(KEY key, MASK mask) { BOOL branch_visible = getBranch()->getVisible(); BOOL handled = getBranch()->handleAcceleratorKey(key, mask); - if (handled && !branch_visible && getVisible()) + if (handled && !branch_visible && isInVisibleChain()) { // flash this menu entry because we triggered an invisible menu item LLMenuHolderGL::setActivatedItem(this); @@ -1637,6 +1615,10 @@ LLMenuScrollItem::LLMenuScrollItem(const Params& p) } LLButton::Params bparams; + + // Disabled the Return key handling by LLMenuScrollItem instead of + // passing the key press to the currently selected menu item. See STORM-385. + bparams.commit_on_return(false); bparams.mouse_opaque(true); bparams.scale_image(false); bparams.click_callback(p.scroll_callback); @@ -1709,7 +1691,8 @@ LLMenuGL::LLMenuGL(const LLMenuGL::Params& p) mSpilloverMenu(NULL), mJumpKey(p.jump_key), mCreateJumpKeys(p.create_jump_keys), - mNeedsArrange(FALSE), + mNeedsArrange(FALSE), + mResetScrollPositionOnShow(true), mShortcutPad(p.shortcut_pad) { typedef boost::tokenizer<boost::char_separator<char> > tokenizer; @@ -1754,7 +1737,7 @@ void LLMenuGL::setCanTearOff(BOOL tear_off) { LLMenuItemTearOffGL::Params p; mTearOffItem = LLUICtrlFactory::create<LLMenuItemTearOffGL>(p); - addChildInBack(mTearOffItem); + addChild(mTearOffItem); } else if (!tear_off && mTearOffItem != NULL) { @@ -1932,9 +1915,15 @@ bool LLMenuGL::scrollItems(EScrollingDirection direction) { item_list_t::reverse_iterator first_visible_item_iter = mItems.rend(); + // Need to scroll through number of actual existing items in menu. + // Otherwise viewer will hang for a time needed to scroll U32_MAX + // times in std::advance(). STORM-659. + size_t nitems = mItems.size(); + U32 scrollable_items = nitems < mMaxScrollableItems ? nitems : mMaxScrollableItems; + // Advance by mMaxScrollableItems back from the end of the list // to make the last item visible. - std::advance(first_visible_item_iter, mMaxScrollableItems); + std::advance(first_visible_item_iter, scrollable_items); mFirstVisibleItem = *first_visible_item_iter; break; } @@ -1977,7 +1966,7 @@ void LLMenuGL::arrange( void ) // *FIX: create the item first and then ask for its dimensions? S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::getFontSansSerif()->getWidth( std::string("More") ); // *TODO: Translate - S32 spillover_item_height = llround(LLFontGL::getFontSansSerif()->getLineHeight()) + MENU_ITEM_PADDING; + S32 spillover_item_height = LLFontGL::getFontSansSerif()->getLineHeight() + MENU_ITEM_PADDING; // Scrolling support item_list_t::iterator first_visible_item_iter; @@ -2611,6 +2600,7 @@ LLMenuItemGL* LLMenuGL::getHighlightedItem() LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disabled) { + if (mItems.empty()) return NULL; // highlighting first item on a torn off menu is the // same as giving focus to it if (!cur_item && getTornOff()) @@ -2711,6 +2701,8 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disabled) { + if (mItems.empty()) return NULL; + // highlighting first item on a torn off menu is the // same as giving focus to it if (!cur_item && getTornOff()) @@ -3045,6 +3037,11 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) const S32 CURSOR_HEIGHT = 22; // Approximate "normal" cursor size const S32 CURSOR_WIDTH = 12; + if(menu->getChildList()->empty()) + { + return; + } + // Save click point for detecting cursor moves before mouse-up. // Must be in local coords to compare with mouseUp events. // If the mouse doesn't move, the menu will stay open ala the Mac. @@ -3052,7 +3049,7 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) S32 mouse_x, mouse_y; // Resetting scrolling position - if (menu->isScrollable()) + if (menu->isScrollable() && menu->isScrollPositionOnShowReset()) { menu->mFirstVisibleItem = NULL; } @@ -3085,11 +3082,8 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) mouse_y + MOUSE_CURSOR_PADDING, CURSOR_WIDTH + MOUSE_CURSOR_PADDING * 2, CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2); - menu->translateIntoRectWithExclusion( menu_region_rect, mouse_rect, FALSE ); + menu->translateIntoRectWithExclusion( menu_region_rect, mouse_rect ); menu->getParent()->sendChildToFront(menu); - - - } ///============================================================================ @@ -3125,7 +3119,10 @@ BOOL LLMenuBarGL::handleAcceleratorKey(KEY key, MASK mask) mAltKeyTrigger = FALSE; } - if(!result && (key == KEY_F10 && mask == MASK_CONTROL) && !gKeyboard->getKeyRepeated(key)) + if(!result + && (key == KEY_F10 && mask == MASK_CONTROL) + && !gKeyboard->getKeyRepeated(key) + && isInVisibleChain()) { if (getHighlightedItem()) { @@ -3428,7 +3425,7 @@ void LLMenuHolderGL::draw() LLUI::pushMatrix(); { - LLUI::translate((F32)item_rect.mLeft, (F32)item_rect.mBottom, 0.f); + LLUI::translate((F32)item_rect.mLeft, (F32)item_rect.mBottom); selecteditem->getMenu()->drawBackground(selecteditem, interpolant); selecteditem->draw(); } @@ -3508,8 +3505,10 @@ BOOL LLMenuHolderGL::handleKey(KEY key, MASK mask, BOOL called_from_parent) else { //highlight first enabled one - pMenu->highlightNextItem(NULL); - handled = true; + if(pMenu->highlightNextItem(NULL)) + { + handled = true; + } } } } @@ -3742,9 +3741,7 @@ public: LLContextMenuBranch(const Params&); virtual ~LLContextMenuBranch() - { - delete mBranch; - } + {} // called to rebuild the draw label virtual void buildDrawLabel( void ); @@ -3752,21 +3749,21 @@ public: // onCommit() - do the primary funcationality of the menu item. virtual void onCommit( void ); - LLContextMenu* getBranch() { return mBranch; } + LLContextMenu* getBranch() { return mBranch.get(); } void setHighlight( BOOL highlight ); protected: void showSubMenu(); - LLContextMenu* mBranch; + LLHandle<LLContextMenu> mBranch; }; LLContextMenuBranch::LLContextMenuBranch(const LLContextMenuBranch::Params& p) : LLMenuItemGL(p), - mBranch( p.branch ) + mBranch( p.branch()->getHandle() ) { - mBranch->hide(); - mBranch->setParentMenuItem(this); + mBranch.get()->hide(); + mBranch.get()->setParentMenuItem(this); } // called to rebuild the draw label @@ -3775,12 +3772,12 @@ void LLContextMenuBranch::buildDrawLabel( void ) { // default enablement is this -- if any of the subitems are // enabled, this item is enabled. JC - U32 sub_count = mBranch->getItemCount(); + U32 sub_count = mBranch.get()->getItemCount(); U32 i; BOOL any_enabled = FALSE; for (i = 0; i < sub_count; i++) { - LLMenuItemGL* item = mBranch->getItem(i); + LLMenuItemGL* item = mBranch.get()->getItem(i); item->buildDrawLabel(); if (item->getEnabled() && !item->getDrawTextDisabled() ) { @@ -3802,13 +3799,13 @@ void LLContextMenuBranch::buildDrawLabel( void ) void LLContextMenuBranch::showSubMenu() { - LLMenuItemGL* menu_item = mBranch->getParentMenuItem(); + LLMenuItemGL* menu_item = mBranch.get()->getParentMenuItem(); if (menu_item != NULL && menu_item->getVisible()) { S32 center_x; S32 center_y; localPointToScreen(getRect().getWidth(), getRect().getHeight() , ¢er_x, ¢er_y); - mBranch->show(center_x, center_y); + mBranch.get()->show(center_x, center_y); } } @@ -3828,7 +3825,7 @@ void LLContextMenuBranch::setHighlight( BOOL highlight ) } else { - mBranch->hide(); + mBranch.get()->hide(); } } @@ -3859,6 +3856,11 @@ void LLContextMenu::setVisible(BOOL visible) // Takes cursor position in screen space? void LLContextMenu::show(S32 x, S32 y) { + if (getChildList()->empty()) + { + // nothing to show, so abort + return; + } // Save click point for detecting cursor moves before mouse-up. // Must be in local coords to compare with mouseUp events. // If the mouse doesn't move, the menu will stay open ala the Mac. diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 35544402f4..36f3ba34b9 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -372,17 +372,16 @@ public: drop_shadow("drop_shadow", true), bg_visible("bg_visible", true), create_jump_keys("create_jump_keys", false), + keep_fixed_size("keep_fixed_size", false), bg_color("bg_color", LLUIColorTable::instance().getColor( "MenuDefaultBgColor" )), scrollable("scrollable", false), max_scrollable_items("max_scrollable_items", U32_MAX), preferred_width("preferred_width", U32_MAX), shortcut_pad("shortcut_pad") - { addSynonym(bg_visible, "opaque"); addSynonym(bg_color, "color"); - - name = "menu"; + addSynonym(can_tear_off, "can_tear_off"); } }; @@ -517,6 +516,9 @@ public: static class LLMenuHolderGL* sMenuContainer; + void resetScrollPositionOnShow(bool reset_scroll_pos) { mResetScrollPositionOnShow = reset_scroll_pos; } + bool isScrollPositionOnShowReset() { return mResetScrollPositionOnShow; } + protected: void createSpilloverBranch(); void cleanupSpilloverBranch(); @@ -566,6 +568,7 @@ private: KEY mJumpKey; BOOL mCreateJumpKeys; S32 mShortcutPad; + bool mResetScrollPositionOnShow; }; // end class LLMenuGL @@ -650,7 +653,7 @@ public: { Params() { - visible = false; + changeDefault(visible, false); } }; @@ -678,9 +681,12 @@ public: BOOL appendContextSubMenu(LLContextMenu *menu); + LLHandle<LLContextMenu> getHandle() { return getDerivedHandle<LLContextMenu>(); } + protected: - BOOL mHoveredAnyItem; - LLMenuItemGL* mHoverItem; + BOOL mHoveredAnyItem; + LLMenuItemGL* mHoverItem; + LLRootHandle<LLContextMenu> mHandle; }; @@ -695,16 +701,7 @@ class LLMenuBarGL : public LLMenuGL { public: struct Params : public LLInitParam::Block<Params, LLMenuGL::Params> - { - Params() - { - can_tear_off = false; - keep_fixed_size = true; - horizontal_layout = true; - visible = true; - drop_shadow = false; - } - }; + {}; LLMenuBarGL( const Params& p ); virtual ~LLMenuBarGL(); @@ -822,13 +819,7 @@ class LLMenuItemTearOffGL : public LLMenuItemGL { public: struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> - { - Params() - { - name = "tear off"; - label = "~~~~~~~~~~~"; - } - }; + {}; LLMenuItemTearOffGL( const Params& ); diff --git a/indra/llui/llmultislider.cpp b/indra/llui/llmultislider.cpp index d4e6091ee0..70bcfb5b4f 100644 --- a/indra/llui/llmultislider.cpp +++ b/indra/llui/llmultislider.cpp @@ -35,6 +35,7 @@ #include "llkeyboard.h" // for the MASK constants #include "llcontrol.h" #include "lluictrlfactory.h" +#include "lluiimage.h" #include <sstream> @@ -65,11 +66,7 @@ LLMultiSlider::Params::Params() mouse_up_callback("mouse_up_callback"), thumb_width("thumb_width"), sliders("slider") -{ - name = "multi_slider_bar"; - mouse_opaque(true); - follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); -} +{} LLMultiSlider::LLMultiSlider(const LLMultiSlider::Params& p) : LLF32UICtrl(p), @@ -510,7 +507,7 @@ void LLMultiSlider::draw() mIt->second.mTop + extra_triangle_height, mIt->second.mLeft + mIt->second.getWidth() / 2, mIt->second.mBottom - extra_triangle_height, - mTriangleColor.get(), TRUE); + mTriangleColor.get() % opacity, TRUE); } } else if (!thumb_imagep) diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index a3df6a3ced..8aa548b974 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -28,6 +28,7 @@ #include "llnotifications.h" #include "llnotificationtemplate.h" +#include "llnotificationvisibilityrule.h" #include "llavatarnamecache.h" #include "llinstantmessage.h" @@ -45,6 +46,7 @@ #include <algorithm> #include <boost/regex.hpp> +#include <boost/foreach.hpp> const std::string NOTIFICATION_PERSIST_VERSION = "0.93"; @@ -81,6 +83,7 @@ LLNotificationForm::FormButton::FormButton() LLNotificationForm::FormInput::FormInput() : type("type"), + text("text"), max_length_chars("max_length_chars"), width("width", 0), value("value") @@ -137,12 +140,6 @@ private: bool filterIgnoredNotifications(LLNotificationPtr notification) { - // filter everything if we are to ignore ALL - if(LLNotifications::instance().getIgnoreAllNotifications()) - { - return false; - } - LLNotificationFormPtr form = notification->getForm(); // Check to see if the user wants to ignore this alert return !notification->getForm()->getIgnored(); @@ -177,6 +174,28 @@ bool handleIgnoredNotification(const LLSD& payload) return false; } +bool defaultResponse(const LLSD& payload) +{ + if (payload["sigtype"].asString() == "add") + { + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); + if (pNotif) + { + // supply default response + pNotif->respond(pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON)); + } + } + return false; +} + +bool visibilityRuleMached(const LLSD& payload) +{ + // This is needed because LLNotifications::isVisibleByRules may have cancelled the notification. + // Returning true here makes LLNotificationChannelBase::updateItem do an early out, which prevents things from happening in the wrong order. + return true; +} + + namespace LLNotificationFilters { // a sample filter @@ -227,7 +246,6 @@ LLNotificationForm::LLNotificationForm(const std::string& name, const LLNotifica LLParamSDParser parser; parser.writeSD(mFormData, p.form_elements); - mFormData = mFormData[""]; if (!mFormData.isArray()) { // change existing contents to a one element array @@ -399,17 +417,48 @@ LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Par mSoundEffect = LLUUID(LLUI::sSettingGroups["config"]->getString(p.sound)); } - for(LLInitParam::ParamIterator<LLNotificationTemplate::UniquenessContext>::const_iterator it = p.unique.contexts.begin(), - end_it = p.unique.contexts.end(); - it != end_it; - ++it) + BOOST_FOREACH(const LLNotificationTemplate::UniquenessContext& context, p.unique.contexts) + { + mUniqueContext.push_back(context.value); + } + + lldebugs << "notification \"" << mName << "\": tag count is " << p.tags.size() << llendl; + + BOOST_FOREACH(const LLNotificationTemplate::Tag& tag, p.tags) { - mUniqueContext.push_back(it->key); + lldebugs << " tag \"" << std::string(tag.value) << "\"" << llendl; + mTags.push_back(tag.value); } mForm = LLNotificationFormPtr(new LLNotificationForm(p.name, p.form_ref.form)); } +LLNotificationVisibilityRule::LLNotificationVisibilityRule(const LLNotificationVisibilityRule::Rule &p) +{ + if (p.show.isChosen()) + { + mType = p.show.type; + mTag = p.show.tag; + mName = p.show.name; + mVisible = true; + } + else if (p.hide.isChosen()) + { + mType = p.hide.type; + mTag = p.hide.tag; + mName = p.hide.name; + mVisible = false; + } + else if (p.respond.isChosen()) + { + mType = p.respond.type; + mTag = p.respond.tag; + mName = p.respond.name; + mVisible = false; + mResponse = p.respond.response; + } +} + LLNotification::LLNotification(const LLNotification::Params& p) : mTimestamp(p.time_stamp), mSubstitutions(p.substitutions), @@ -679,6 +728,25 @@ bool LLNotification::hasUniquenessConstraints() const return (mTemplatep ? mTemplatep->mUnique : false); } +bool LLNotification::matchesTag(const std::string& tag) +{ + bool result = false; + + if(mTemplatep) + { + std::list<std::string>::iterator it; + for(it = mTemplatep->mTags.begin(); it != mTemplatep->mTags.end(); it++) + { + if((*it) == tag) + { + result = true; + break; + } + } + } + + return result; +} void LLNotification::setIgnored(bool ignore) { @@ -719,13 +787,19 @@ bool LLNotification::isEquivalentTo(LLNotificationPtr that) const { const LLSD& these_substitutions = this->getSubstitutions(); const LLSD& those_substitutions = that->getSubstitutions(); + const LLSD& this_payload = this->getPayload(); + const LLSD& that_payload = that->getPayload(); // highlander bit sez there can only be one of these for (std::vector<std::string>::const_iterator it = mTemplatep->mUniqueContext.begin(), end_it = mTemplatep->mUniqueContext.end(); it != end_it; ++it) { - if (these_substitutions.get(*it).asString() != those_substitutions.get(*it).asString()) + // if templates differ in either substitution strings or payload with the given field name + // then they are considered inequivalent + // use of get() avoids converting the LLSD value to a map as the [] operator would + if (these_substitutions.get(*it).asString() != those_substitutions.get(*it).asString() + || this_payload.get(*it).asString() != that_payload.get(*it).asString()) { return false; } @@ -1064,12 +1138,12 @@ std::string LLNotificationChannel::summarize() // LLNotifications implementation // --- LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything, - LLNotificationComparators::orderByUUID()), - mIgnoreAllNotifications(false) + LLNotificationComparators::orderByUUID()), + mIgnoreAllNotifications(false) { LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); - - mListener.reset(new LLNotificationsListener(*this)); + + mListener.reset(new LLNotificationsListener(*this)); } @@ -1115,16 +1189,18 @@ bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif) bool LLNotifications::uniqueHandler(const LLSD& payload) { + std::string cmd = payload["sigtype"]; + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); if (pNotif && pNotif->hasUniquenessConstraints()) { - if (payload["sigtype"].asString() == "add") + if (cmd == "add") { // not a duplicate according to uniqueness criteria, so we keep it // and store it for future uniqueness checks mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif)); } - else if (payload["sigtype"].asString() == "delete") + else if (cmd == "delete") { mUniqueNotifications.erase(pNotif->getName()); } @@ -1137,12 +1213,16 @@ bool LLNotifications::failedUniquenessTest(const LLSD& payload) { LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); - if (!pNotif || !pNotif->hasUniquenessConstraints()) + std::string cmd = payload["sigtype"]; + + if (!pNotif || cmd != "add") { return false; } - // checks against existing unique notifications + // Update the existing unique notification with the data from this particular instance... + // This guarantees that duplicate notifications will be collapsed to the one + // most recently triggered for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); existing_it != mUniqueNotifications.end(); ++existing_it) @@ -1155,7 +1235,7 @@ bool LLNotifications::failedUniquenessTest(const LLSD& payload) // of this unique notification and update it existing_notification->updateFrom(pNotif); // then delete the new one - pNotif->cancel(); + cancel(pNotif); } } @@ -1184,6 +1264,7 @@ LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelN void LLNotifications::initSingleton() { loadTemplates(); + loadVisibilityRules(); createDefaultChannels(); } @@ -1191,15 +1272,19 @@ void LLNotifications::createDefaultChannels() { // now construct the various channels AFTER loading the notifications, // because the history channel is going to rewrite the stored notifications file - LLNotificationChannel::buildChannel("Expiration", "", + LLNotificationChannel::buildChannel("Enabled", "", + !boost::bind(&LLNotifications::getIgnoreAllNotifications, this)); + LLNotificationChannel::buildChannel("Expiration", "Enabled", boost::bind(&LLNotifications::expirationFilter, this, _1)); - LLNotificationChannel::buildChannel("Unexpired", "", + LLNotificationChannel::buildChannel("Unexpired", "Enabled", !boost::bind(&LLNotifications::expirationFilter, this, _1)); // use negated bind LLNotificationChannel::buildChannel("Unique", "Unexpired", boost::bind(&LLNotifications::uniqueFilter, this, _1)); LLNotificationChannel::buildChannel("Ignore", "Unique", filterIgnoredNotifications); - LLNotificationChannel::buildChannel("Visible", "Ignore", + LLNotificationChannel::buildChannel("VisibilityRules", "Ignore", + boost::bind(&LLNotifications::isVisibleByRules, this, _1)); + LLNotificationChannel::buildChannel("Visible", "VisibilityRules", &LLNotificationFilters::includeEverything); // create special persistent notification channel @@ -1207,30 +1292,22 @@ void LLNotifications::createDefaultChannels() new LLPersistentNotificationChannel(); // connect action methods to these channels + LLNotifications::instance().getChannel("Enabled")-> + connectFailedFilter(&defaultResponse); LLNotifications::instance().getChannel("Expiration")-> connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); // uniqueHandler slot should be added as first slot of the signal due to // usage LLStopWhenHandled combiner in LLStandardSignal LLNotifications::instance().getChannel("Unique")-> connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); -// failedUniquenessTest slot isn't necessary -// LLNotifications::instance().getChannel("Unique")-> -// connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); + LLNotifications::instance().getChannel("Unique")-> + connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); LLNotifications::instance().getChannel("Ignore")-> connectFailedFilter(&handleIgnoredNotification); + LLNotifications::instance().getChannel("VisibilityRules")-> + connectFailedFilter(&visibilityRuleMached); } -bool LLNotifications::addTemplate(const std::string &name, - LLNotificationTemplatePtr theTemplate) -{ - if (mTemplates.count(name)) - { - llwarns << "LLNotifications -- attempted to add template '" << name << "' twice." << llendl; - return false; - } - mTemplates[name] = theTemplate; - return true; -} LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name) { @@ -1278,7 +1355,6 @@ LLNotifications::TemplateNames LLNotifications::getTemplateNames() const typedef std::map<std::string, std::string> StringMap; void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements) { - //llwarns << "replaceSubstitutionStrings" << llendl; // walk the list of attributes looking for replacements for (LLXMLAttribList::iterator it=node->mAttributes.begin(); it != node->mAttributes.end(); ++it) @@ -1292,13 +1368,12 @@ void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements) if (found != replacements.end()) { replacement = found->second; - //llwarns << "replaceSubstituionStrings: value: " << value << " repl: " << replacement << llendl; - + lldebugs << "replaceSubstitutionStrings: value: \"" << value << "\" repl: \"" << replacement << "\"." << llendl; it->second->setValue(replacement); } else { - llwarns << "replaceSubstituionStrings FAILURE: value: " << value << " repl: " << replacement << llendl; + llwarns << "replaceSubstitutionStrings FAILURE: could not find replacement \"" << value << "\"." << llendl; } } } @@ -1317,80 +1392,124 @@ void replaceFormText(LLNotificationForm::Params& form, const std::string& patter { form.ignore.text = replace; } - for (LLInitParam::ParamIterator<LLNotificationForm::FormElement>::iterator it = form.form_elements.elements.begin(), - end_it = form.form_elements.elements.end(); - it != end_it; - ++it) + + BOOST_FOREACH(LLNotificationForm::FormElement& element, form.form_elements.elements) { - if (it->button.isChosen() && it->button.text() == pattern) + if (element.button.isChosen() && element.button.text() == pattern) { - it->button.text = replace; + element.button.text = replace; } } } +void addPathIfExists(const std::string& new_path, std::vector<std::string>& paths) +{ + if (gDirUtilp->fileExists(new_path)) + { + paths.push_back(new_path); + } +} + bool LLNotifications::loadTemplates() { - const std::string xml_filename = "notifications.xml"; - std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getXUIPaths().front(), xml_filename); + llinfos << "Reading notifications template" << llendl; + std::vector<std::string> search_paths; + + std::string skin_relative_path = gDirUtilp->getDirDelimiter() + LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + "notifications.xml"; + std::string localized_skin_relative_path = gDirUtilp->getDirDelimiter() + LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + "notifications.xml"; + + addPathIfExists(gDirUtilp->getDefaultSkinDir() + skin_relative_path, search_paths); + addPathIfExists(gDirUtilp->getDefaultSkinDir() + localized_skin_relative_path, search_paths); + addPathIfExists(gDirUtilp->getSkinDir() + skin_relative_path, search_paths); + addPathIfExists(gDirUtilp->getSkinDir() + localized_skin_relative_path, search_paths); + addPathIfExists(gDirUtilp->getUserSkinDir() + skin_relative_path, search_paths); + addPathIfExists(gDirUtilp->getUserSkinDir() + localized_skin_relative_path, search_paths); + std::string base_filename = search_paths.front(); LLXMLNodePtr root; - BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); + BOOL success = LLXMLNode::getLayeredXMLNode(root, search_paths); if (!success || root.isNull() || !root->hasName( "notifications" )) { - llerrs << "Problem reading UI Notifications file: " << full_filename << llendl; + llerrs << "Problem reading UI Notifications file: " << base_filename << llendl; return false; } LLNotificationTemplate::Notifications params; LLXUIParser parser; - parser.readXUI(root, params, full_filename); + parser.readXUI(root, params, base_filename); + + if(!params.validateBlock()) + { + llerrs << "Problem reading UI Notifications file: " << base_filename << llendl; + return false; + } mTemplates.clear(); - for(LLInitParam::ParamIterator<LLNotificationTemplate::GlobalString>::const_iterator it = params.strings.begin(), end_it = params.strings.end(); - it != end_it; - ++it) + BOOST_FOREACH(LLNotificationTemplate::GlobalString& string, params.strings) { - mGlobalStrings[it->name] = it->value; + mGlobalStrings[string.name] = string.value; } std::map<std::string, LLNotificationForm::Params> form_templates; - for(LLInitParam::ParamIterator<LLNotificationTemplate::Template>::const_iterator it = params.templates.begin(), end_it = params.templates.end(); - it != end_it; - ++it) + BOOST_FOREACH(LLNotificationTemplate::Template& notification_template, params.templates) { - form_templates[it->name] = it->form; + form_templates[notification_template.name] = notification_template.form; } - for(LLInitParam::ParamIterator<LLNotificationTemplate::Params>::iterator it = params.notifications.begin(), end_it = params.notifications.end(); - it != end_it; - ++it) + BOOST_FOREACH(LLNotificationTemplate::Params& notification, params.notifications) { - if (it->form_ref.form_template.isChosen()) + if (notification.form_ref.form_template.isChosen()) { // replace form contents from template - it->form_ref.form = form_templates[it->form_ref.form_template.name]; - if(it->form_ref.form_template.yes_text.isProvided()) + notification.form_ref.form = form_templates[notification.form_ref.form_template.name]; + if(notification.form_ref.form_template.yes_text.isProvided()) { - replaceFormText(it->form_ref.form, "$yestext", it->form_ref.form_template.yes_text); + replaceFormText(notification.form_ref.form, "$yestext", notification.form_ref.form_template.yes_text); } - if(it->form_ref.form_template.no_text.isProvided()) + if(notification.form_ref.form_template.no_text.isProvided()) { - replaceFormText(it->form_ref.form, "$notext", it->form_ref.form_template.no_text); + replaceFormText(notification.form_ref.form, "$notext", notification.form_ref.form_template.no_text); } - if(it->form_ref.form_template.cancel_text.isProvided()) + if(notification.form_ref.form_template.cancel_text.isProvided()) { - replaceFormText(it->form_ref.form, "$canceltext", it->form_ref.form_template.cancel_text); + replaceFormText(notification.form_ref.form, "$canceltext", notification.form_ref.form_template.cancel_text); } - if(it->form_ref.form_template.ignore_text.isProvided()) + if(notification.form_ref.form_template.ignore_text.isProvided()) { - replaceFormText(it->form_ref.form, "$ignoretext", it->form_ref.form_template.ignore_text); + replaceFormText(notification.form_ref.form, "$ignoretext", notification.form_ref.form_template.ignore_text); } } - addTemplate(it->name, LLNotificationTemplatePtr(new LLNotificationTemplate(*it))); + mTemplates[notification.name] = LLNotificationTemplatePtr(new LLNotificationTemplate(notification)); + } + + llinfos << "...done" << llendl; + + return true; +} + +bool LLNotifications::loadVisibilityRules() +{ + const std::string xml_filename = "notification_visibility.xml"; + std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getXUIPaths().front(), xml_filename); + + LLNotificationVisibilityRule::Rules params; + LLSimpleXUIParser parser; + parser.readXUI(full_filename, params); + + if(!params.validateBlock()) + { + llerrs << "Problem reading UI Notification Visibility Rules file: " << full_filename << llendl; + return false; + } + + mVisibilityRules.clear(); + + BOOST_FOREACH(LLNotificationVisibilityRule::Rule& rule, params.rules) + { + mVisibilityRules.push_back(LLNotificationVisibilityRulePtr(new LLNotificationVisibilityRule(rule))); } return true; @@ -1399,7 +1518,7 @@ bool LLNotifications::loadTemplates() // Add a simple notification (from XUI) void LLNotifications::addFromCallback(const LLSD& name) { - add(LLNotification::Params().name(name.asString())); + add(name.asString(), LLSD(), LLSD()); } LLNotificationPtr LLNotifications::add(const std::string& name, @@ -1457,7 +1576,7 @@ void LLNotifications::add(const LLNotificationPtr pNotif) void LLNotifications::cancel(LLNotificationPtr pNotif) { - if (pNotif == NULL) return; + if (pNotif == NULL || pNotif->isCancelled()) return; LLNotificationSet::iterator it=mItems.find(pNotif); if (it == mItems.end()) @@ -1508,7 +1627,7 @@ LLNotificationPtr LLNotifications::find(LLUUID uuid) LLNotificationSet::iterator it=mItems.find(target); if (it == mItems.end()) { - llwarns << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << llendl; + LL_DEBUGS("Notifications") << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << llendl; return LLNotificationPtr((LLNotification*)NULL); } else @@ -1546,6 +1665,93 @@ bool LLNotifications::getIgnoreAllNotifications() return mIgnoreAllNotifications; } +bool LLNotifications::isVisibleByRules(LLNotificationPtr n) +{ + if(n->isRespondedTo()) + { + // This avoids infinite recursion in the case where the filter calls respond() + return true; + } + + VisibilityRuleList::iterator it; + + for(it = mVisibilityRules.begin(); it != mVisibilityRules.end(); it++) + { + // An empty type/tag/name string will match any notification, so only do the comparison when the string is non-empty in the rule. + lldebugs + << "notification \"" << n->getName() << "\" " + << "testing against " << ((*it)->mVisible?"show":"hide") << " rule, " + << "name = \"" << (*it)->mName << "\" " + << "tag = \"" << (*it)->mTag << "\" " + << "type = \"" << (*it)->mType << "\" " + << llendl; + + if(!(*it)->mType.empty()) + { + if((*it)->mType != n->getType()) + { + // Type doesn't match, so skip this rule. + continue; + } + } + + if(!(*it)->mTag.empty()) + { + // check this notification's tag(s) against it->mTag and continue if no match is found. + if(!n->matchesTag((*it)->mTag)) + { + // This rule's non-empty tag didn't match one of the notification's tags. Skip this rule. + continue; + } + } + + if(!(*it)->mName.empty()) + { + // check this notification's name against the notification's name and continue if no match is found. + if((*it)->mName != n->getName()) + { + // This rule's non-empty name didn't match the notification. Skip this rule. + continue; + } + } + + // If we got here, the rule matches. Don't evaluate subsequent rules. + if(!(*it)->mVisible) + { + // This notification is being hidden. + + if((*it)->mResponse.empty()) + { + // Response property is empty. Cancel this notification. + lldebugs << "cancelling notification " << n->getName() << llendl; + + cancel(n); + } + else + { + // Response property is not empty. Return the specified response. + LLSD response = n->getResponseTemplate(LLNotification::WITHOUT_DEFAULT_BUTTON); + // TODO: verify that the response template has an item with the correct name + response[(*it)->mResponse] = true; + + lldebugs << "responding to notification " << n->getName() << " with response = " << response << llendl; + + n->respond(response); + } + + return false; + } + + // If we got here, exit the loop and return true. + break; + } + + lldebugs << "allowing notification " << n->getName() << llendl; + + return true; +} + + // --- // END OF LLNotifications implementation // ========================================================= diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 524cff70e8..3df2efcac3 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -195,12 +195,13 @@ public: Mandatory<std::string> type; Optional<S32> width; Optional<S32> max_length_chars; + Optional<std::string> text; Optional<std::string> value; FormInput(); }; - struct FormElement : public LLInitParam::Choice<FormElement> + struct FormElement : public LLInitParam::ChoiceBlock<FormElement> { Alternative<FormButton> button; Alternative<FormInput> input; @@ -270,6 +271,11 @@ struct LLNotificationTemplate; // with smart pointers typedef boost::shared_ptr<LLNotificationTemplate> LLNotificationTemplatePtr; + +struct LLNotificationVisibilityRule; + +typedef boost::shared_ptr<LLNotificationVisibilityRule> LLNotificationVisibilityRulePtr; + /** * @class LLNotification * @brief The object that expresses the details of a notification @@ -306,7 +312,7 @@ public: Optional<LLNotificationContext*> context; Optional<void*> responder; - struct Functor : public LLInitParam::Choice<Functor> + struct Functor : public LLInitParam::ChoiceBlock<Functor> { Alternative<std::string> name; Alternative<LLNotificationFunctorRegistry::ResponseFunctor> function; @@ -506,7 +512,7 @@ public: std::string getLabel() const; std::string getURL() const; S32 getURLOption() const; - S32 getURLOpenExternally() const; + S32 getURLOpenExternally() const; const LLNotificationFormPtr getForm(); @@ -578,6 +584,8 @@ public: bool hasUniquenessConstraints() const; + bool matchesTag(const std::string& tag); + virtual ~LLNotification() {} }; @@ -682,7 +690,7 @@ typedef std::multimap<std::string, LLNotificationPtr> LLNotificationMap; // Abstract base class (interface) for a channel; also used for the master container. // This lets us arrange channels into a call hierarchy. -// We maintain a heirarchy of notification channels; events are always started at the top +// We maintain a hierarchy of notification channels; events are always started at the top // and propagated through the hierarchy only if they pass a filter. // Any channel can be created with a parent. A null parent (empty string) means it's // tied to the root of the tree (the LLNotifications class itself). @@ -855,9 +863,14 @@ class LLNotifications : friend class LLSingleton<LLNotifications>; public: - // load notification descriptions from file; + // load all notification descriptions from file + // calling more than once will overwrite existing templates + // but never delete a template + bool loadTemplates(); + + // load visibility rules from file; // OK to call more than once because it will reload - bool loadTemplates(); + bool loadVisibilityRules(); // Add a simple notification (from XUI) void addFromCallback(const LLSD& name); @@ -904,6 +917,8 @@ public: // test for existence bool templateExists(const std::string& name); + typedef std::list<LLNotificationVisibilityRulePtr> VisibilityRuleList; + void forceResponse(const LLNotification::Params& params, S32 option); void createDefaultChannels(); @@ -919,6 +934,8 @@ public: void setIgnoreAllNotifications(bool ignore); bool getIgnoreAllNotifications(); + bool isVisibleByRules(LLNotificationPtr pNotification); + private: // we're a singleton, so we don't have a public constructor LLNotifications(); @@ -934,10 +951,10 @@ private: LLNotificationChannelPtr pHistoryChannel; LLNotificationChannelPtr pExpirationChannel; - // put your template in - bool addTemplate(const std::string& name, LLNotificationTemplatePtr theTemplate); TemplateMap mTemplates; + VisibilityRuleList mVisibilityRules; + std::string mFileName; LLNotificationMap mUniqueNotifications; diff --git a/indra/llui/llnotificationtemplate.h b/indra/llui/llnotificationtemplate.h index 6bc0d2aaff..fb50c9c123 100644 --- a/indra/llui/llnotificationtemplate.h +++ b/indra/llui/llnotificationtemplate.h @@ -74,11 +74,13 @@ struct LLNotificationTemplate struct UniquenessContext : public LLInitParam::Block<UniquenessContext> { - Mandatory<std::string> key; + Mandatory<std::string> value; UniquenessContext() - : key("key") - {} + : value("value") + { + addSynonym(value, "key"); + } }; @@ -86,10 +88,10 @@ struct LLNotificationTemplate { private: // this idiom allows - // <notification unique="true"> + // <notification> <unique/> </notification> // as well as - // <notification> <unique> <context key=""/> </unique>... - Optional<bool> dummy_val; + // <notification> <unique> <context></context> </unique>... + Optional<LLInitParam::Flag> dummy_val; public: Multiple<UniquenessContext> contexts; @@ -145,7 +147,7 @@ struct LLNotificationTemplate {} }; - struct FormRef : public LLInitParam::Choice<FormRef> + struct FormRef : public LLInitParam::ChoiceBlock<FormRef> { Alternative<LLNotificationForm::Params> form; Alternative<TemplateRef> form_template; @@ -156,6 +158,15 @@ struct LLNotificationTemplate {} }; + struct Tag : public LLInitParam::Block<Tag> + { + Mandatory<std::string> value; + + Tag() + : value("value") + {} + }; + struct Params : public LLInitParam::Block<Params> { Mandatory<std::string> name; @@ -173,6 +184,7 @@ struct LLNotificationTemplate Optional<FormRef> form_ref; Optional<ENotificationPriority, NotificationPriorityValues> priority; + Multiple<Tag> tags; Params() @@ -189,7 +201,8 @@ struct LLNotificationTemplate expire_option("expireOption", -1), url("url"), unique("unique"), - form_ref("") + form_ref(""), + tags("tag") {} }; @@ -232,8 +245,8 @@ struct LLNotificationTemplate // (used for things like progress indications, or repeating warnings // like "the grid is going down in N minutes") bool mUnique; - // if we want to be unique only if a certain part of the payload is constant - // specify the field names for the payload. The notification will only be + // if we want to be unique only if a certain part of the payload or substitutions args + // are constant specify the field names for the payload. The notification will only be // combined if all of the fields named in the context are identical in the // new and the old notification; otherwise, the notification will be // duplicated. This is to support suppressing duplicate offers from the same @@ -276,6 +289,8 @@ struct LLNotificationTemplate // this is loaded as a name, but looked up to get the UUID upon template load. // If null, it wasn't specified. LLUUID mSoundEffect; + // List of tags that rules can match against. + std::list<std::string> mTags; }; #endif //LL_LLNOTIFICATION_TEMPLATE_H diff --git a/indra/llui/llnotificationvisibilityrule.h b/indra/llui/llnotificationvisibilityrule.h new file mode 100644 index 0000000000..78788a275c --- /dev/null +++ b/indra/llui/llnotificationvisibilityrule.h @@ -0,0 +1,104 @@ +/** +* @file llnotificationvisibility.h +* @brief Rules for +* @author Monroe +* +* $LicenseInfo:firstyear=2010&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$ +*/ + +#ifndef LL_LLNOTIFICATION_VISIBILITY_RULE_H +#define LL_LLNOTIFICATION_VISIBILITY_RULE_H + +#include "llinitparam.h" +//#include "llnotifications.h" + + + +// This is the class of object read from the XML file (notification_visibility.xml, +// from the appropriate local language directory). +struct LLNotificationVisibilityRule +{ + struct Filter : public LLInitParam::Block<Filter> + { + Optional<std::string> type, + tag, + name; + + Filter() + : type("type"), + tag("tag"), + name("name") + {} + }; + + struct Respond : public LLInitParam::Block<Respond, Filter> + { + Mandatory<std::string> response; + + Respond() + : response("response") + {} + }; + + struct Rule : public LLInitParam::ChoiceBlock<Rule> + { + Alternative<Filter> show; + Alternative<Filter> hide; + Alternative<Respond> respond; + + Rule() + : show("show"), + hide("hide"), + respond("respond") + {} + }; + + struct Rules : public LLInitParam::Block<Rules> + { + Multiple<Rule> rules; + + Rules() + : rules("") + {} + }; + + LLNotificationVisibilityRule(const Rule& p); + + // If true, this rule makes matching notifications visible. Otherwise, it makes them invisible. + bool mVisible; + + // Which response to give when making a notification invisible. An empty string means the notification should be cancelled instead of responded to. + std::string mResponse; + + // String to match against the notification's "type". An empty string matches all notifications. + std::string mType; + + // String to match against the notification's tag(s). An empty string matches all notifications. + std::string mTag; + + // String to match against the notification's name. An empty string matches all notifications. + std::string mName; + +}; + +#endif //LL_LLNOTIFICATION_VISIBILITY_RULE_H + diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index 900e2c789e..00318cec6b 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -87,9 +87,9 @@ LLPanel::Params::Params() filename("filename"), class_name("class"), help_topic("help_topic"), - visible_callback("visible_callback") + visible_callback("visible_callback"), + accepts_badge("accepts_badge") { - name = "panel"; addSynonym(background_visible, "bg_visible"); addSynonym(has_border, "border_visible"); addSynonym(label, "title"); @@ -98,6 +98,7 @@ LLPanel::Params::Params() LLPanel::LLPanel(const LLPanel::Params& p) : LLUICtrl(p), + LLBadgeHolder(p.accepts_badge), mBgVisible(p.background_visible), mBgOpaque(p.background_opaque), mBgOpaqueColor(p.bg_opaque_color()), @@ -121,8 +122,6 @@ LLPanel::LLPanel(const LLPanel::Params& p) { addBorder(p.border); } - - mPanelHandle.bind(this); } LLPanel::~LLPanel() @@ -194,6 +193,8 @@ void LLPanel::draw() // draw background if( mBgVisible ) { + alpha = getCurrentTransparency(); + LLRect local_rect = getLocalRect(); if (mBgOpaque ) { @@ -434,7 +435,7 @@ void LLPanel::initFromParams(const LLPanel::Params& p) //and LLView::initFromParams will use them to set visible and enabled setVisible(p.visible); setEnabled(p.enabled); - + setFocusRoot(p.focus_root); setSoundFlags(p.sound_flags); // control_name, tab_stop, focus_lost_callback, initial_value, rect, enabled, visible @@ -483,6 +484,8 @@ void LLPanel::initFromParams(const LLPanel::Params& p) mBgAlphaImage = p.bg_alpha_image(); mBgOpaqueImageOverlay = p.bg_opaque_image_overlay; mBgAlphaImageOverlay = p.bg_alpha_image_overlay; + + setAcceptsBadge(p.accepts_badge); } static LLFastTimer::DeclareTimer FTM_PANEL_SETUP("Panel Setup"); @@ -509,9 +512,6 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu if (!xml_filename.empty()) { - LLUICtrlFactory::instance().pushFileName(xml_filename); - - LLFastTimer timer(FTM_EXTERNAL_PANEL_LOAD); if (output_node) { //if we are exporting, we want to export the current xml @@ -524,6 +524,9 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu return TRUE; } + LLUICtrlFactory::instance().pushFileName(xml_filename); + + LLFastTimer timer(FTM_EXTERNAL_PANEL_LOAD); if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) { llwarns << "Couldn't parse panel from: " << xml_filename << llendl; diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index c1a1a06f39..f620201020 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -32,8 +32,10 @@ #include "llcallbackmap.h" #include "lluictrl.h" #include "llviewborder.h" +#include "lluiimage.h" #include "lluistring.h" #include "v4color.h" +#include "llbadgeholder.h" #include <list> #include <queue> @@ -50,7 +52,7 @@ class LLUIImage; * With or without border, * Can contain LLUICtrls. */ -class LLPanel : public LLUICtrl +class LLPanel : public LLUICtrl, public LLBadgeHolder { public: struct LocalizedString : public LLInitParam::Block<LocalizedString> @@ -88,13 +90,12 @@ public: Multiple<LocalizedString> strings; Optional<CommitCallbackParam> visible_callback; + + Optional<bool> accepts_badge; Params(); }; - // valid children for LLPanel are stored in this registry - typedef LLDefaultChildRegistry child_registry_t; - protected: friend class LLUICtrlFactory; // RN: for some reason you can't just use LLUICtrlFactory::getDefaultParams as a default argument in VC8 @@ -134,6 +135,8 @@ public: const LLColor4& getBackgroundColor() const { return mBgOpaqueColor; } void setTransparentColor(const LLColor4& color) { mBgAlphaColor = color; } const LLColor4& getTransparentColor() const { return mBgAlphaColor; } + void setBackgroundImage(LLUIImage* image) { mBgOpaqueImage = image; } + void setTransparentImage(LLUIImage* image) { mBgAlphaImage = image; } LLPointer<LLUIImage> getBackgroundImage() const { return mBgOpaqueImage; } LLPointer<LLUIImage> getTransparentImage() const { return mBgAlphaImage; } LLColor4 getBackgroundImageOverlay() { return mBgOpaqueImageOverlay; } @@ -152,7 +155,7 @@ public: void setCtrlsEnabled(BOOL b); - LLHandle<LLPanel> getHandle() const { return mPanelHandle; } + LLHandle<LLPanel> getHandle() const { return getDerivedHandle<LLPanel>(); } const LLCallbackMap::map_t& getFactoryMap() const { return mFactoryMap; } @@ -261,6 +264,9 @@ protected: std::string mHelpTopic; // the name of this panel's help topic to display in the Help Viewer typedef std::deque<const LLCallbackMap::map_t*> factory_stack_t; static factory_stack_t sFactoryStack; + + // for setting the xml filename when building panel in context dependent cases + std::string mXMLFilename; private: BOOL mBgVisible; // any background at all? @@ -274,13 +280,10 @@ private: LLViewBorder* mBorder; LLButton* mDefaultBtn; LLUIString mLabel; - LLRootHandle<LLPanel> mPanelHandle; typedef std::map<std::string, std::string> ui_string_map_t; ui_string_map_t mUIStrings; - // for setting the xml filename when building panel in context dependent cases - std::string mXMLFilename; }; // end class LLPanel diff --git a/indra/llui/llprogressbar.cpp b/indra/llui/llprogressbar.cpp index aaa328754d..84a890edfa 100644 --- a/indra/llui/llprogressbar.cpp +++ b/indra/llui/llprogressbar.cpp @@ -38,6 +38,7 @@ #include "llfocusmgr.h" #include "lluictrlfactory.h" +#include "lluiimage.h" static LLDefaultChildRegistry::Register<LLProgressBar> r("progress_bar"); @@ -50,7 +51,7 @@ LLProgressBar::Params::Params() LLProgressBar::LLProgressBar(const LLProgressBar::Params& p) -: LLView(p), +: LLUICtrl(p), mImageBar(p.image_bar), mImageFill(p.image_fill), mColorBackground(p.color_bg()), @@ -80,7 +81,7 @@ void LLProgressBar::draw() mImageFill->draw(progress_rect, bar_color); } -void LLProgressBar::setPercent(const F32 percent) +void LLProgressBar::setValue(const LLSD& value) { - mPercentDone = llclamp(percent, 0.f, 100.f); + mPercentDone = llclamp((F32)value.asReal(), 0.f, 100.f); } diff --git a/indra/llui/llprogressbar.h b/indra/llui/llprogressbar.h index 13297f7493..a8ec83ea00 100644 --- a/indra/llui/llprogressbar.h +++ b/indra/llui/llprogressbar.h @@ -27,14 +27,15 @@ #ifndef LL_LLPROGRESSBAR_H #define LL_LLPROGRESSBAR_H -#include "llview.h" #include "llframetimer.h" +#include "lluictrl.h" +#include "lluiimage.h" class LLProgressBar - : public LLView + : public LLUICtrl { public: - struct Params : public LLInitParam::Block<Params, LLView::Params> + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { Optional<LLUIImage*> image_bar, image_fill; @@ -47,7 +48,7 @@ public: LLProgressBar(const Params&); virtual ~LLProgressBar(); - void setPercent(const F32 percent); + void setValue(const LLSD& value); /*virtual*/ void draw(); diff --git a/indra/llui/llradiogroup.cpp b/indra/llui/llradiogroup.cpp index cc348fdc63..95a7d09382 100644 --- a/indra/llui/llradiogroup.cpp +++ b/indra/llui/llradiogroup.cpp @@ -69,14 +69,11 @@ protected: static LLWidgetNameRegistry::StaticRegistrar register_radio_item(&typeid(LLRadioGroup::ItemParams), "radio_item"); LLRadioGroup::Params::Params() -: has_border("draw_border"), +: allow_deselect("allow_deselect"), items("item") { addSynonym(items, "radio_item"); - name = "radio_group"; - mouse_opaque = true; - follows.flags = FOLLOWS_LEFT | FOLLOWS_TOP; // radio items are not tabbable until they are selected tab_stop = false; } @@ -85,18 +82,8 @@ LLRadioGroup::LLRadioGroup(const LLRadioGroup::Params& p) : LLUICtrl(p), mFont(p.font.isProvided() ? p.font() : LLFontGL::getFontSansSerifSmall()), mSelectedIndex(-1), - mHasBorder(p.has_border) -{ - if (mHasBorder) - { - LLViewBorder::Params params; - params.name("radio group border"); - params.rect(LLRect(0, getRect().getHeight(), getRect().getWidth(), 0)); - params.bevel_style(LLViewBorder::BEVEL_NONE); - LLViewBorder * vb = LLUICtrlFactory::create<LLViewBorder> (params); - addChild (vb); - } -} + mAllowDeselect(p.allow_deselect) +{} void LLRadioGroup::initFromParams(const Params& p) { @@ -106,7 +93,10 @@ void LLRadioGroup::initFromParams(const Params& p) { LLRadioGroup::ItemParams item_params(*it); - item_params.font.setIfNotProvided(mFont); // apply radio group font by default + if (!item_params.font.isProvided()) + { + item_params.font = mFont; // apply radio group font by default + } item_params.commit_callback.function = boost::bind(&LLRadioGroup::onClickButton, this, _1); item_params.from_xui = p.from_xui; if (p.from_xui) @@ -184,7 +174,7 @@ void LLRadioGroup::setIndexEnabled(S32 index, BOOL enabled) BOOL LLRadioGroup::setSelectedIndex(S32 index, BOOL from_event) { - if (index < 0 || (S32)mRadioButtons.size() <= index ) + if ((S32)mRadioButtons.size() <= index ) { return FALSE; } @@ -202,13 +192,16 @@ BOOL LLRadioGroup::setSelectedIndex(S32 index, BOOL from_event) mSelectedIndex = index; - LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex]; - radio_item->setTabStop(true); - radio_item->setValue( TRUE ); - - if (hasFocus()) + if (mSelectedIndex >= 0) { - mRadioButtons[mSelectedIndex]->focusFirstItem(FALSE, FALSE); + LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex]; + radio_item->setTabStop(true); + radio_item->setValue( TRUE ); + + if (hasFocus()) + { + radio_item->focusFirstItem(FALSE, FALSE); + } } if (!from_event) @@ -307,8 +300,15 @@ void LLRadioGroup::onClickButton(LLUICtrl* ctrl) LLRadioCtrl* radio = *iter; if (radio == clicked_radio) { - // llinfos << "clicked button " << index << llendl; - setSelectedIndex(index); + if (index == mSelectedIndex && mAllowDeselect) + { + // don't select anything + setSelectedIndex(-1); + } + else + { + setSelectedIndex(index); + } // BUG: Calls click callback even if button didn't actually change onCommit(); @@ -346,7 +346,7 @@ void LLRadioGroup::setValue( const LLSD& value ) } else { - llwarns << "LLRadioGroup::setValue: value not found: " << value.asString() << llendl; + setSelectedIndex(-1, TRUE); } } } diff --git a/indra/llui/llradiogroup.h b/indra/llui/llradiogroup.h index 0588900600..8bd5698538 100644 --- a/indra/llui/llradiogroup.h +++ b/indra/llui/llradiogroup.h @@ -49,7 +49,7 @@ public: struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { - Optional<bool> has_border; + Optional<bool> allow_deselect; Multiple<ItemParams, AtLeast<1> > items; Params(); }; @@ -73,7 +73,6 @@ public: void setIndexEnabled(S32 index, BOOL enabled); // return the index value of the selected item S32 getSelectedIndex() const { return mSelectedIndex; } - // set the index value programatically BOOL setSelectedIndex(S32 index, BOOL from_event = FALSE); @@ -103,12 +102,13 @@ public: /*virtual*/ BOOL operateOnAll(EOperation op); private: - const LLFontGL* mFont; - S32 mSelectedIndex; + const LLFontGL* mFont; + S32 mSelectedIndex; + typedef std::vector<class LLRadioCtrl*> button_list_t; - button_list_t mRadioButtons; + button_list_t mRadioButtons; - BOOL mHasBorder; + bool mAllowDeselect; // user can click on an already selected option to deselect it }; #endif diff --git a/indra/llui/llresizebar.cpp b/indra/llui/llresizebar.cpp index 02f60c76fa..87aeb4d7a7 100644 --- a/indra/llui/llresizebar.cpp +++ b/indra/llui/llresizebar.cpp @@ -79,6 +79,8 @@ LLResizeBar::LLResizeBar(const LLResizeBar::Params& p) BOOL LLResizeBar::handleMouseDown(S32 x, S32 y, MASK mask) { + if (!canResize()) return FALSE; + // Route future Mouse messages here preemptively. (Release on mouse up.) // No handler needed for focus lost since this clas has no state that depends on it. gFocusMgr.setMouseCapture( this ); @@ -243,7 +245,7 @@ BOOL LLResizeBar::handleHover(S32 x, S32 y, MASK mask) handled = TRUE; } - if( handled ) + if( handled && canResize() ) { switch( mSide ) { diff --git a/indra/llui/llresizebar.h b/indra/llui/llresizebar.h index 0725fbd846..6daf191918 100644 --- a/indra/llui/llresizebar.h +++ b/indra/llui/llresizebar.h @@ -70,6 +70,7 @@ public: void setResizeLimits( S32 min_size, S32 max_size ) { mMinSize = min_size; mMaxSize = max_size; } void setEnableSnapping(BOOL enable) { mSnappingEnabled = enable; } void setAllowDoubleClickSnapping(BOOL allow) { mAllowDoubleClickSnapping = allow; } + bool canResize() { return getEnabled() && mMaxSize > mMinSize; } private: S32 mDragLastScreenX; diff --git a/indra/llui/llresizehandle.h b/indra/llui/llresizehandle.h index 531eb1db61..7541b9e6c0 100644 --- a/indra/llui/llresizehandle.h +++ b/indra/llui/llresizehandle.h @@ -55,7 +55,7 @@ public: virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); void setResizeLimits( S32 min_width, S32 min_height ) { mMinWidth = min_width; mMinHeight = min_height; } - + private: BOOL pointInHandle( S32 x, S32 y ); diff --git a/indra/llui/llresmgr.cpp b/indra/llui/llresmgr.cpp index 39385786bc..820e7cb26a 100644 --- a/indra/llui/llresmgr.cpp +++ b/indra/llui/llresmgr.cpp @@ -337,7 +337,7 @@ LLLocale::LLLocale(const std::string& locale_string) char* new_locale_string = setlocale( LC_ALL, locale_string.c_str()); if ( new_locale_string == NULL) { - llwarns << "Failed to set locale " << locale_string << llendl; + LL_WARNS_ONCE("LLLocale") << "Failed to set locale " << locale_string << LL_ENDL; setlocale(LC_ALL, SYSTEM_LOCALE.c_str()); } //else diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp index 3a867a10a7..5d3bf7a670 100644 --- a/indra/llui/llscrollbar.cpp +++ b/indra/llui/llscrollbar.cpp @@ -63,9 +63,7 @@ LLScrollbar::Params::Params() right_button("right_button"), bg_visible("bg_visible", false), bg_color("bg_color", LLColor4::black) -{ - tab_stop = false; -} +{} LLScrollbar::LLScrollbar(const Params & p) : LLUICtrl(p), diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index 3146418a7d..9b7e30bb04 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -74,11 +74,7 @@ LLScrollContainer::Params::Params() min_auto_scroll_rate("min_auto_scroll_rate", 100), max_auto_scroll_rate("max_auto_scroll_rate", 1000), reserve_scroll_corner("reserve_scroll_corner", false) -{ - name = "scroll_container"; - mouse_opaque(true); - tab_stop(false); -} +{} // Default constructor @@ -227,6 +223,15 @@ BOOL LLScrollContainer::handleKeyHere(KEY key, MASK mask) return FALSE; } +BOOL LLScrollContainer::handleUnicodeCharHere(llwchar uni_char) +{ + if (mScrolledView && mScrolledView->handleUnicodeCharHere(uni_char)) + { + return TRUE; + } + return FALSE; +} + BOOL LLScrollContainer::handleScrollWheel( S32 x, S32 y, S32 clicks ) { // Give event to my child views - they may have scroll bars @@ -373,19 +378,24 @@ void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height if (!mHideScrollbar) { - if( *visible_height < doc_height ) + // Note: 1 pixel change can happen on final animation and should not trigger + // the display of sliders. + if ((doc_height - *visible_height) > 1) { *show_v_scrollbar = TRUE; *visible_width -= scrollbar_size; } - - if( *visible_width < doc_width ) + if ((doc_width - *visible_width) > 1) { *show_h_scrollbar = TRUE; *visible_height -= scrollbar_size; + // The view inside the scroll container should not be extended + // to container's full height to ensure the correct computation + // of *show_v_scrollbar after subtracting horizontal scrollbar_size. + // Must retest now that visible_height has changed - if( !*show_v_scrollbar && (*visible_height < doc_height) ) + if( !*show_v_scrollbar && ((doc_height - *visible_height) > 1) ) { *show_v_scrollbar = TRUE; *visible_width -= scrollbar_size; @@ -419,62 +429,66 @@ void LLScrollContainer::draw() focusFirstItem(); } - // Draw background - if( mIsOpaque ) - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4fv( mBackgroundColor.get().mV ); - gl_rect_2d( mInnerRect ); - } - - // Draw mScrolledViews and update scroll bars. - // get a scissor region ready, and draw the scrolling view. The - // scissor region ensures that we don't draw outside of the bounds - // of the rectangle. - if( mScrolledView ) + if (getRect().isValid()) { - updateScroll(); - - // Draw the scrolled area. + // Draw background + if( mIsOpaque ) { - S32 visible_width = 0; - S32 visible_height = 0; - BOOL show_v_scrollbar = FALSE; - BOOL show_h_scrollbar = FALSE; - calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - - LLLocalClipRect clip(LLRect(mInnerRect.mLeft, - mInnerRect.mBottom + (show_h_scrollbar ? scrollbar_size : 0) + visible_height, - mInnerRect.mRight - (show_v_scrollbar ? scrollbar_size: 0), - mInnerRect.mBottom + (show_h_scrollbar ? scrollbar_size : 0) - )); - drawChild(mScrolledView); - } - } + F32 alpha = getCurrentTransparency(); - // Highlight border if a child of this container has keyboard focus - if( mBorder->getVisible() ) - { - mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus(this) ); - } - - // Draw all children except mScrolledView - // Note: scrollbars have been adjusted by above drawing code - for (child_list_const_reverse_iter_t child_iter = getChildList()->rbegin(); - child_iter != getChildList()->rend(); ++child_iter) - { - LLView *viewp = *child_iter; - if( sDebugRects ) + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(mInnerRect, mBackgroundColor.get() % alpha); + } + + // Draw mScrolledViews and update scroll bars. + // get a scissor region ready, and draw the scrolling view. The + // scissor region ensures that we don't draw outside of the bounds + // of the rectangle. + if( mScrolledView ) { - sDepth++; + updateScroll(); + + // Draw the scrolled area. + { + S32 visible_width = 0; + S32 visible_height = 0; + BOOL show_v_scrollbar = FALSE; + BOOL show_h_scrollbar = FALSE; + calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + LLLocalClipRect clip(LLRect(mInnerRect.mLeft, + mInnerRect.mBottom + (show_h_scrollbar ? scrollbar_size : 0) + visible_height, + mInnerRect.mRight - (show_v_scrollbar ? scrollbar_size: 0), + mInnerRect.mBottom + (show_h_scrollbar ? scrollbar_size : 0) + )); + drawChild(mScrolledView); + } } - if( (viewp != mScrolledView) && viewp->getVisible() ) + + // Highlight border if a child of this container has keyboard focus + if( mBorder->getVisible() ) { - drawChild(viewp); + mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus(this) ); } - if( sDebugRects ) + + // Draw all children except mScrolledView + // Note: scrollbars have been adjusted by above drawing code + for (child_list_const_reverse_iter_t child_iter = getChildList()->rbegin(); + child_iter != getChildList()->rend(); ++child_iter) { - sDepth--; + LLView *viewp = *child_iter; + if( sDebugRects ) + { + sDepth++; + } + if( (viewp != mScrolledView) && viewp->getVisible() ) + { + drawChild(viewp); + } + if( sDebugRects ) + { + sDepth--; + } } } } // end draw diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h index 46a71a7e30..d87c95b3d7 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -91,7 +91,7 @@ public: void setReserveScrollCorner( BOOL b ) { mReserveScrollCorner = b; } LLRect getVisibleContentRect(); LLRect getContentWindowRect(); - const LLRect& getScrolledViewRect() const { return mScrolledView ? mScrolledView->getRect() : LLRect::null; } + virtual const LLRect getScrolledViewRect() const { return mScrolledView ? mScrolledView->getRect() : LLRect::null; } void pageUp(S32 overlap = 0); void pageDown(S32 overlap = 0); void goToTop(); @@ -103,6 +103,7 @@ public: // LLView functionality virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); virtual BOOL handleKeyHere(KEY key, MASK mask); + virtual BOOL handleUnicodeCharHere(llwchar uni_char); virtual BOOL handleScrollWheel( S32 x, S32 y, S32 clicks ); virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, @@ -115,6 +116,9 @@ public: bool autoScroll(S32 x, S32 y); +protected: + LLView* mScrolledView; + private: // internal scrollbar handlers virtual void scrollHorizontal( S32 new_pos ); @@ -123,7 +127,6 @@ private: void calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) const; LLScrollbar* mScrollbar[SCROLLBAR_COUNT]; - LLView* mScrolledView; S32 mSize; BOOL mIsOpaque; LLUIColor mBackgroundColor; diff --git a/indra/llui/llscrollingpanellist.h b/indra/llui/llscrollingpanellist.h index 8f569c2a58..e8df176ec3 100644 --- a/indra/llui/llscrollingpanellist.h +++ b/indra/llui/llscrollingpanellist.h @@ -51,12 +51,7 @@ class LLScrollingPanelList : public LLUICtrl { public: struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> - { - Params() - { - name = "scrolling_panel_list"; - } - }; + {}; LLScrollingPanelList(const Params& p) : LLUICtrl(p) {} diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp index 9d25c7180d..8000efad0e 100644 --- a/indra/llui/llscrolllistcell.cpp +++ b/indra/llui/llscrolllistcell.cpp @@ -232,7 +232,7 @@ BOOL LLScrollListText::getVisible() const //virtual S32 LLScrollListText::getHeight() const { - return llround(mFont->getLineHeight()); + return mFont->getLineHeight(); } @@ -306,7 +306,7 @@ void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_col break; } LLRect highlight_rect(left - 2, - llround(mFont->getLineHeight()) + 1, + mFont->getLineHeight() + 1, left + mFont->getWidth(mText.getString(), mHighlightOffset, mHighlightCount) + 1, 1); mRoundedRectImage->draw(highlight_rect, highlight_color); @@ -329,7 +329,7 @@ void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_col break; } mFont->render(mText.getWString(), 0, - start_x, 2.f, + start_x, 0.f, display_color, mFontAlignment, LLFontGL::BOTTOM, diff --git a/indra/llui/llscrolllistcolumn.cpp b/indra/llui/llscrolllistcolumn.cpp index 696e4a2bb1..07a6dfaa10 100644 --- a/indra/llui/llscrolllistcolumn.cpp +++ b/indra/llui/llscrolllistcolumn.cpp @@ -46,10 +46,7 @@ static LLWidgetNameRegistry::StaticRegistrar sRegisterColumnHeaderParams(&typeid //--------------------------------------------------------------------------- LLScrollColumnHeader::Params::Params() : column("column") -{ - name = "column_header"; - tab_stop(false); -} +{} LLScrollColumnHeader::LLScrollColumnHeader(const LLScrollColumnHeader::Params& p) diff --git a/indra/llui/llscrolllistcolumn.h b/indra/llui/llscrolllistcolumn.h index e2711ac75a..b4d4a6d05e 100644 --- a/indra/llui/llscrolllistcolumn.h +++ b/indra/llui/llscrolllistcolumn.h @@ -95,7 +95,7 @@ public: Optional<ESortDirection, SortNames> sort_direction; Optional<bool> sort_ascending; - struct Width : public LLInitParam::Choice<Width> + struct Width : public LLInitParam::ChoiceBlock<Width> { Alternative<bool> dynamic_width; Alternative<S32> pixel_width; @@ -112,7 +112,7 @@ public: Optional<Width> width; // either an image or label is used in column header - struct Header : public LLInitParam::Choice<Header> + struct Header : public LLInitParam::ChoiceBlock<Header> { Alternative<std::string> label; Alternative<LLUIImage*> image; @@ -135,7 +135,7 @@ public: halign("halign", LLFontGL::LEFT) { // default choice to "dynamic_width" - width.dynamic_width = true; + changeDefault(width.dynamic_width, true); addSynonym(sort_column, "sort"); } diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 7df7c13dc0..b3e1b63db5 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -147,12 +147,9 @@ LLScrollListCtrl::Params::Params() highlighted_color("highlighted_color"), contents(""), scroll_bar_bg_visible("scroll_bar_bg_visible"), - scroll_bar_bg_color("scroll_bar_bg_color") - , border("border") -{ - name = "scroll_list"; - mouse_opaque = true; -} + scroll_bar_bg_color("scroll_bar_bg_color"), + border("border") +{} LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) : LLUICtrl(p), @@ -178,6 +175,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) mBorder(NULL), mSortCallback(NULL), mPopupMenu(NULL), + mCommentTextView(NULL), mNumDynamicWidthColumns(0), mTotalStaticColumnWidth(0), mTotalColumnPadding(0), @@ -322,6 +320,7 @@ LLScrollListCtrl::~LLScrollListCtrl() delete mSortCallback; std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer()); } @@ -478,7 +477,12 @@ void LLScrollListCtrl::updateLayout() getRect().getWidth() - 2 * mBorderThickness, getRect().getHeight() - (2 * mBorderThickness ) - heading_size ); - getChildView("comment_text")->setShape(mItemListRect); + if (mCommentTextView == NULL) + { + mCommentTextView = getChildView("comment_text"); + } + + mCommentTextView->setShape(mItemListRect); // how many lines of content in a single "page" S32 page_lines = getLinesPerPage(); @@ -1482,8 +1486,9 @@ void LLScrollListCtrl::draw() // Draw background if (mBackgroundVisible) { + F32 alpha = getCurrentTransparency(); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() : mBgReadOnlyColor.get() ); + gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() % alpha : mBgReadOnlyColor.get() % alpha ); } if (mColumnsDirty) @@ -2370,10 +2375,10 @@ void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar ) void LLScrollListCtrl::sortByColumn(const std::string& name, BOOL ascending) { - std::map<std::string, LLScrollListColumn>::iterator itor = mColumns.find(name); + column_map_t::iterator itor = mColumns.find(name); if (itor != mColumns.end()) { - sortByColumnIndex((*itor).second.mIndex, ascending); + sortByColumnIndex((*itor).second->mIndex, ascending); } } @@ -2419,11 +2424,11 @@ void LLScrollListCtrl::dirtyColumns() // just in case someone indexes into it immediately mColumnsIndexed.resize(mColumns.size()); - std::map<std::string, LLScrollListColumn>::iterator column_itor; + column_map_t::iterator column_itor; for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) { - LLScrollListColumn *column = &column_itor->second; - mColumnsIndexed[column_itor->second.mIndex] = column; + LLScrollListColumn *column = column_itor->second; + mColumnsIndexed[column_itor->second->mIndex] = column; } } @@ -2499,7 +2504,7 @@ void LLScrollListCtrl::copy() { buffer += (*itor)->getContentsCSV() + "\n"; } - gClipboard.copyFromSubstring(utf8str_to_wstring(buffer), 0, buffer.length()); + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(buffer), 0, buffer.length()); } // virtual @@ -2581,8 +2586,8 @@ void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params if (mColumns.find(name) == mColumns.end()) { // Add column - mColumns[name] = LLScrollListColumn(column_params, this); - LLScrollListColumn* new_column = &mColumns[name]; + mColumns[name] = new LLScrollListColumn(column_params, this); + LLScrollListColumn* new_column = mColumns[name]; new_column->mIndex = mColumns.size()-1; // Add button @@ -2604,14 +2609,14 @@ void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params S32 top = mItemListRect.mTop; S32 left = mItemListRect.mLeft; - for (std::map<std::string, LLScrollListColumn>::iterator itor = mColumns.begin(); + for (column_map_t::iterator itor = mColumns.begin(); itor != mColumns.end(); ++itor) { - if (itor->second.mIndex < new_column->mIndex && - itor->second.getWidth() > 0) + if (itor->second->mIndex < new_column->mIndex && + itor->second->getWidth() > 0) { - left += itor->second.getWidth() + mColumnPadding; + left += itor->second->getWidth() + mColumnPadding; } } @@ -2667,8 +2672,8 @@ void LLScrollListCtrl::onClickColumn(void *userdata) if (column->mSortingColumn != column->mName && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end()) { - LLScrollListColumn& info_redir = parent->mColumns[column->mSortingColumn]; - column_index = info_redir.mIndex; + LLScrollListColumn* info_redir = parent->mColumns[column->mSortingColumn]; + column_index = info_redir->mIndex; } // if this column is the primary sort key, reverse the direction @@ -2701,16 +2706,17 @@ BOOL LLScrollListCtrl::hasSortOrder() const void LLScrollListCtrl::clearColumns() { - std::map<std::string, LLScrollListColumn>::iterator itor; + column_map_t::iterator itor; for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) { - LLScrollColumnHeader *header = itor->second.mHeader; + LLScrollColumnHeader *header = itor->second->mHeader; if (header) { removeChild(header); delete header; } } + std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer()); mColumns.clear(); mSortColumns.clear(); mTotalStaticColumnWidth = 0; @@ -2744,7 +2750,7 @@ LLScrollListColumn* LLScrollListCtrl::getColumn(const std::string& name) column_map_t::iterator column_itor = mColumns.find(name); if (column_itor != mColumns.end()) { - return &column_itor->second; + return column_itor->second; } return NULL; } @@ -2805,12 +2811,15 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS new_column.width.pixel_width = cell_p.width; } addColumn(new_column); - columnp = &mColumns[column]; + columnp = mColumns[column]; new_item->setNumColumns(mColumns.size()); } S32 index = columnp->mIndex; - cell_p.width.setIfNotProvided(columnp->getWidth()); + if (!cell_p.width.isProvided()) + { + cell_p.width = columnp->getWidth(); + } LLScrollListCell* cell = LLScrollListCell::create(cell_p); @@ -2842,7 +2851,7 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value)); if (cell) { - LLScrollListColumn* columnp = &(mColumns.begin()->second); + LLScrollListColumn* columnp = mColumns.begin()->second; new_item->setColumn(0, cell); if (columnp->mHeader @@ -2857,10 +2866,10 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS // add dummy cells for missing columns for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it) { - S32 column_idx = column_it->second.mIndex; + S32 column_idx = column_it->second->mIndex; if (new_item->getColumn(column_idx) == NULL) { - LLScrollListColumn* column_ptr = &column_it->second; + LLScrollListColumn* column_ptr = column_it->second; LLScrollListCell::Params cell_p; cell_p.width = column_ptr->getWidth(); diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 8a2f893ba2..ae8aea9245 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -480,6 +480,8 @@ private: S32 mHighlightedItem; class LLViewBorder* mBorder; LLContextMenu *mPopupMenu; + + LLView *mCommentTextView; LLWString mSearchString; LLFrameTimer mSearchTimer; @@ -491,7 +493,7 @@ private: mutable bool mSorted; - typedef std::map<std::string, LLScrollListColumn> column_map_t; + typedef std::map<std::string, LLScrollListColumn*> column_map_t; column_map_t mColumns; BOOL mDirty; diff --git a/indra/llui/llscrolllistitem.cpp b/indra/llui/llscrolllistitem.cpp index d95752e31c..5a1e96ab03 100644 --- a/indra/llui/llscrolllistitem.cpp +++ b/indra/llui/llscrolllistitem.cpp @@ -138,7 +138,7 @@ void LLScrollListItem::draw(const LLRect& rect, const LLColor4& fg_color, const LLUI::pushMatrix(); { - LLUI::translate((F32) cur_x, (F32) rect.mBottom, 0.0f); + LLUI::translate((F32) cur_x, (F32) rect.mBottom); cell->draw( fg_color, highlight_color ); } diff --git a/indra/llui/llscrolllistitem.h b/indra/llui/llscrolllistitem.h index 611df729b4..13655b5873 100644 --- a/indra/llui/llscrolllistitem.h +++ b/indra/llui/llscrolllistitem.h @@ -33,10 +33,10 @@ #include "v4color.h" #include "llinitparam.h" #include "llscrolllistcell.h" +#include "llcoord.h" #include <vector> -class LLCoordGL; class LLCheckBoxCtrl; class LLResizeBar; class LLScrollListCtrl; diff --git a/indra/llui/llsdparam.cpp b/indra/llui/llsdparam.cpp index f97f80ab6c..0e29873bb0 100644 --- a/indra/llui/llsdparam.cpp +++ b/indra/llui/llsdparam.cpp @@ -34,6 +34,9 @@ static LLInitParam::Parser::parser_read_func_map_t sReadFuncs; static LLInitParam::Parser::parser_write_func_map_t sWriteFuncs; static LLInitParam::Parser::parser_inspect_func_map_t sInspectFuncs; +static const LLSD NO_VALUE_MARKER; + +LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR("LLSD to LLInitParam conversion"); // // LLParamSDParser @@ -45,6 +48,7 @@ LLParamSDParser::LLParamSDParser() if (sReadFuncs.empty()) { + registerParserFuncs<LLInitParam::Flag>(readFlag, &LLParamSDParser::writeFlag); registerParserFuncs<S32>(readS32, &LLParamSDParser::writeTypedValue<S32>); registerParserFuncs<U32>(readU32, &LLParamSDParser::writeU32Param); registerParserFuncs<F32>(readF32, &LLParamSDParser::writeTypedValue<F32>); @@ -59,62 +63,52 @@ LLParamSDParser::LLParamSDParser() } // special case handling of U32 due to ambiguous LLSD::assign overload -bool LLParamSDParser::writeU32Param(LLParamSDParser::parser_t& parser, const void* val_ptr, const parser_t::name_stack_t& name_stack) +bool LLParamSDParser::writeU32Param(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) { LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser); if (!sdparser.mWriteRootSD) return false; - LLSD* sd_to_write = sdparser.getSDWriteNode(name_stack); - if (!sd_to_write) return false; + parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); + LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); + sd_to_write.assign((S32)*((const U32*)val_ptr)); - sd_to_write->assign((S32)*((const U32*)val_ptr)); return true; } +bool LLParamSDParser::writeFlag(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) +{ + LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser); + if (!sdparser.mWriteRootSD) return false; + + parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); + LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); + + return true; +} + +void LLParamSDParser::submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack) +{ + mCurReadSD = &sd; + block.submitValue(name_stack, *this); +} + void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent) { mCurReadSD = NULL; mNameStack.clear(); setParseSilently(silent); - readSDValues(sd, block); + LLParamSDParserUtilities::readSDValues(boost::bind(&LLParamSDParser::submit, this, boost::ref(block), _1, _2), sd, mNameStack); + //readSDValues(sd, block); } void LLParamSDParser::writeSD(LLSD& sd, const LLInitParam::BaseBlock& block) { mNameStack.clear(); mWriteRootSD = &sd; - block.serializeBlock(*this); -} -void LLParamSDParser::readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block) -{ - if (sd.isMap()) - { - for (LLSD::map_const_iterator it = sd.beginMap(); - it != sd.endMap(); - ++it) - { - mNameStack.push_back(make_pair(it->first, newParseGeneration())); - readSDValues(it->second, block); - mNameStack.pop_back(); - } - } - else if (sd.isArray()) - { - for (LLSD::array_const_iterator it = sd.beginArray(); - it != sd.endArray(); - ++it) - { - mNameStack.back().second = newParseGeneration(); - readSDValues(*it, block); - } - } - else - { - mCurReadSD = &sd; - block.submitValue(mNameStack, *this); - } + name_stack_t name_stack; + block.serializeBlock(*this, name_stack); } /*virtual*/ std::string LLParamSDParser::getCurrentElementName() @@ -130,82 +124,14 @@ void LLParamSDParser::readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block return full_name; } -LLSD* LLParamSDParser::getSDWriteNode(const parser_t::name_stack_t& name_stack) -{ - //TODO: implement nested LLSD writing - LLSD* sd_to_write = mWriteRootSD; - bool new_traversal = false; - for (name_stack_t::const_iterator it = name_stack.begin(), prev_it = mNameStack.begin(); - it != name_stack.end(); - ++it) - { - bool new_array_entry = false; - if (prev_it == mNameStack.end()) - { - new_traversal = true; - } - else - { - if (!new_traversal // have not diverged yet from previous trace - && prev_it->first == it->first // names match - && prev_it->second != it->second) // versions differ - { - // name stacks match, but version numbers differ in last place. - // create a different entry at this point using an LLSD array - new_array_entry = true; - } - if (prev_it->first != it->first // names differ - || prev_it->second != it->second) // versions differ - { - // at this point we have diverged from our last trace - // so any elements referenced here are new - new_traversal = true; - } - } - LLSD* child_sd = &(*sd_to_write)[it->first]; - - if (child_sd->isArray()) - { - if (new_traversal) - { - // write to new element at end - sd_to_write = &(*child_sd)[child_sd->size()]; - } - else - { - // write to last of existing elements, or first element if empty - sd_to_write = &(*child_sd)[llmax(0, child_sd->size() - 1)]; - } - } - else - { - if (new_array_entry && !child_sd->isArray()) - { - // copy child contents into first element of an array - LLSD new_array = LLSD::emptyArray(); - new_array.append(*child_sd); - // assign array to slot that previously held the single value - *child_sd = new_array; - // return next element in that array - sd_to_write = &((*child_sd)[1]); - } - else - { - sd_to_write = child_sd; - } - } - if (prev_it != mNameStack.end()) - { - ++prev_it; - } - } - mNameStack = name_stack; - - //llinfos << ll_pretty_print_sd(*mWriteRootSD) << llendl; - return sd_to_write; +bool LLParamSDParser::readFlag(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + return self.mCurReadSD == &NO_VALUE_MARKER; } + bool LLParamSDParser::readS32(Parser& parser, void* val_ptr) { LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); @@ -285,3 +211,132 @@ bool LLParamSDParser::readSD(Parser& parser, void* val_ptr) *((LLSD*)val_ptr) = *self.mCurReadSD; return true; } + +// static +LLSD& LLParamSDParserUtilities::getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range) +{ + LLSD* sd_to_write = &input; + + for (LLInitParam::Parser::name_stack_t::iterator it = name_stack_range.first; + it != name_stack_range.second; + ++it) + { + bool new_traversal = it->second; + + LLSD* child_sd = it->first.empty() ? sd_to_write : &(*sd_to_write)[it->first]; + + if (child_sd->isArray()) + { + if (new_traversal) + { + // write to new element at end + sd_to_write = &(*child_sd)[child_sd->size()]; + } + else + { + // write to last of existing elements, or first element if empty + sd_to_write = &(*child_sd)[llmax(0, child_sd->size() - 1)]; + } + } + else + { + if (new_traversal + && child_sd->isDefined() + && !child_sd->isArray()) + { + // copy child contents into first element of an array + LLSD new_array = LLSD::emptyArray(); + new_array.append(*child_sd); + // assign array to slot that previously held the single value + *child_sd = new_array; + // return next element in that array + sd_to_write = &((*child_sd)[1]); + } + else + { + sd_to_write = child_sd; + } + } + it->second = false; + } + + return *sd_to_write; +} + +//static +void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack) +{ + if (sd.isMap()) + { + for (LLSD::map_const_iterator it = sd.beginMap(); + it != sd.endMap(); + ++it) + { + stack.push_back(make_pair(it->first, true)); + readSDValues(cb, it->second, stack); + stack.pop_back(); + } + } + else if (sd.isArray()) + { + for (LLSD::array_const_iterator it = sd.beginArray(); + it != sd.endArray(); + ++it) + { + stack.back().second = true; + readSDValues(cb, *it, stack); + } + } + else if (sd.isUndefined()) + { + if (!cb.empty()) + { + cb(NO_VALUE_MARKER, stack); + } + } + else + { + if (!cb.empty()) + { + cb(sd, stack); + } + } +} + +//static +void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd) +{ + LLInitParam::Parser::name_stack_t stack = LLInitParam::Parser::name_stack_t(); + readSDValues(cb, sd, stack); +} +namespace LLInitParam +{ + // LLSD specialization + // block param interface + bool ParamValue<LLSD, TypeValues<LLSD>, false>::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name) + { + LLSD& sd = LLParamSDParserUtilities::getSDWriteNode(mValue, name_stack); + + LLSD::String string; + + if (p.readValue<LLSD::String>(string)) + { + sd = string; + return true; + } + return false; + } + + //static + void ParamValue<LLSD, TypeValues<LLSD>, false>::serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack) + { + p.writeValue<LLSD::String>(sd.asString(), name_stack); + } + + void ParamValue<LLSD, TypeValues<LLSD>, false>::serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block) const + { + // read from LLSD value and serialize out to parser (which could be LLSD, XUI, etc) + Parser::name_stack_t stack; + LLParamSDParserUtilities::readSDValues(boost::bind(&serializeElement, boost::ref(p), _1, _2), mValue, stack); + } +} diff --git a/indra/llui/llsdparam.h b/indra/llui/llsdparam.h index 97e8b58e49..3dfc6d020e 100644 --- a/indra/llui/llsdparam.h +++ b/indra/llui/llsdparam.h @@ -29,6 +29,16 @@ #define LL_LLSDPARAM_H #include "llinitparam.h" +#include "boost/function.hpp" + +struct LLParamSDParserUtilities +{ + static LLSD& getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range); + + typedef boost::function<void (const LLSD&, LLInitParam::Parser::name_stack_t&)> read_sd_cb_t; + static void readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack); + static void readSDValues(read_sd_cb_t cb, const LLSD& sd); +}; class LLParamSDParser : public LLInitParam::Parser @@ -45,25 +55,25 @@ public: /*virtual*/ std::string getCurrentElementName(); private: - void readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block); + void submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack); template<typename T> - static bool writeTypedValue(Parser& parser, const void* val_ptr, const parser_t::name_stack_t& name_stack) + static bool writeTypedValue(Parser& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) { LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser); if (!sdparser.mWriteRootSD) return false; - LLSD* sd_to_write = sdparser.getSDWriteNode(name_stack); - if (!sd_to_write) return false; + LLInitParam::Parser::name_stack_range_t range(name_stack.begin(), name_stack.end()); + LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); - sd_to_write->assign(*((const T*)val_ptr)); + sd_to_write.assign(*((const T*)val_ptr)); return true; } - LLSD* getSDWriteNode(const parser_t::name_stack_t& name_stack); - - static bool writeU32Param(Parser& parser, const void* value_ptr, const parser_t::name_stack_t& name_stack); + static bool writeU32Param(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); + static bool writeFlag(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); + static bool readFlag(Parser& parser, void* val_ptr); static bool readS32(Parser& parser, void* val_ptr); static bool readU32(Parser& parser, void* val_ptr); static bool readF32(Parser& parser, void* val_ptr); @@ -81,22 +91,36 @@ private: LLSD* mCurWriteSD; }; + +extern LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR; template<typename T> class LLSDParamAdapter : public T +{ +public: + LLSDParamAdapter() {} + LLSDParamAdapter(const LLSD& sd) { - public: - LLSDParamAdapter() {} - LLSDParamAdapter(const LLSD& sd) - { - LLParamSDParser parser; - parser.readSD(sd, *this); - } + LLFastTimer _(FTM_SD_PARAM_ADAPTOR); + LLParamSDParser parser; + // don't spam for implicit parsing of LLSD, as we want to allow arbitrary freeform data and ignore most of it + bool parse_silently = true; + parser.readSD(sd, *this, parse_silently); + } + + operator LLSD() const + { + LLParamSDParser parser; + LLSD sd; + parser.writeSD(sd, *this); + return sd; + } - LLSDParamAdapter(const T& val) - { - T::operator=(val); - } - }; + LLSDParamAdapter(const T& val) + : T(val) + { + T::operator=(val); + } +}; #endif // LL_LLSDPARAM_H diff --git a/indra/llui/llsearcheditor.h b/indra/llui/llsearcheditor.h index f5c3b532c4..c2d7916938 100644 --- a/indra/llui/llsearcheditor.h +++ b/indra/llui/llsearcheditor.h @@ -55,9 +55,7 @@ public: search_button_visible("search_button_visible"), clear_button("clear_button"), clear_button_visible("clear_button_visible") - { - name = "search_editor"; - } + {} }; void setCommitOnFocusLost(BOOL b) { if (mSearchEditor) mSearchEditor->setCommitOnFocusLost(b); } diff --git a/indra/llui/llslider.cpp b/indra/llui/llslider.cpp index 013950a5ad..db72234f94 100644 --- a/indra/llui/llslider.cpp +++ b/indra/llui/llslider.cpp @@ -54,9 +54,7 @@ LLSlider::Params::Params() track_highlight_vertical_image("track_highlight_vertical_image"), mouse_down_callback("mouse_down_callback"), mouse_up_callback("mouse_up_callback") -{ - follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); -} +{} LLSlider::LLSlider(const LLSlider::Params& p) : LLF32UICtrl(p), diff --git a/indra/llui/llslider.h b/indra/llui/llslider.h index 68823ed68e..700c17ea3e 100644 --- a/indra/llui/llslider.h +++ b/indra/llui/llslider.h @@ -29,6 +29,7 @@ #include "llf32uictrl.h" #include "v4color.h" +#include "lluiimage.h" class LLSlider : public LLF32UICtrl { diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp index d760178e35..583ed1ed2e 100644 --- a/indra/llui/llsliderctrl.cpp +++ b/indra/llui/llsliderctrl.cpp @@ -76,8 +76,14 @@ LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) } LLRect label_rect( left, top, label_width, bottom ); LLTextBox::Params params(p.slider_label); - params.rect.setIfNotProvided(label_rect); - params.font.setIfNotProvided(p.font); + if (!params.rect.isProvided()) + { + params.rect = label_rect; + } + if (!params.font.isProvided()) + { + params.font = p.font; + } params.initial_value(p.label()); mLabelBox = LLUICtrlFactory::create<LLTextBox> (params); addChild(mLabelBox); @@ -113,15 +119,33 @@ LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) S32 slider_left = label_width ? label_width + sliderctrl_spacing : 0; LLSlider::Params slider_p(p.slider_bar); slider_p.name("slider_bar"); - slider_p.rect.setIfNotProvided(LLRect(slider_left,top,slider_right,bottom)); - slider_p.initial_value.setIfNotProvided(p.initial_value().asReal()); - slider_p.min_value.setIfNotProvided(p.min_value); - slider_p.max_value.setIfNotProvided(p.max_value); - slider_p.increment.setIfNotProvided(p.increment); - slider_p.orientation.setIfNotProvided(p.orientation); + if (!slider_p.rect.isProvided()) + { + slider_p.rect = LLRect(slider_left,top,slider_right,bottom); + } + if (!slider_p.initial_value.isProvided()) + { + slider_p.initial_value = p.initial_value().asReal(); + } + if (!slider_p.min_value.isProvided()) + { + slider_p.min_value = p.min_value; + } + if (!slider_p.max_value.isProvided()) + { + slider_p.max_value = p.max_value; + } + if (!slider_p.increment.isProvided()) + { + slider_p.increment = p.increment; + } + if (!slider_p.orientation.isProvided()) + { + slider_p.orientation = p.orientation; + } - slider_p.commit_callback.function(&LLSliderCtrl::onSliderCommit); - slider_p.control_name(p.control_name); + slider_p.commit_callback.function = &LLSliderCtrl::onSliderCommit; + slider_p.control_name = p.control_name; slider_p.mouse_down_callback( p.mouse_down_callback ); slider_p.mouse_up_callback( p.mouse_up_callback ); mSlider = LLUICtrlFactory::create<LLSlider> (slider_p); @@ -134,8 +158,15 @@ LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) if( p.can_edit_text() ) { LLLineEditor::Params line_p(p.value_editor); - line_p.rect.setIfNotProvided(text_rect); - line_p.font.setIfNotProvided(p.font); + if (!line_p.rect.isProvided()) + { + line_p.rect = text_rect; + } + if (!line_p.font.isProvided()) + { + line_p.font = p.font; + } + line_p.commit_callback.function(&LLSliderCtrl::onEditorCommit); line_p.prevalidate_callback(&LLTextValidate::validateFloat); mEditor = LLUICtrlFactory::create<LLLineEditor>(line_p); @@ -149,8 +180,14 @@ LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) else { LLTextBox::Params text_p(p.value_text); - text_p.rect.setIfNotProvided(text_rect); - text_p.font.setIfNotProvided(p.font); + if (!text_p.rect.isProvided()) + { + text_p.rect = text_rect; + } + if (!text_p.font.isProvided()) + { + text_p.font = p.font; + } mTextBox = LLUICtrlFactory::create<LLTextBox>(text_p); addChild(mTextBox); } diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index 6b4e9cf923..934879cdfd 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -44,7 +44,7 @@ #include "llresmgr.h" #include "lluictrlfactory.h" -const U32 MAX_STRING_LENGTH = 32; +const U32 MAX_STRING_LENGTH = 255; static LLDefaultChildRegistry::Register<LLSpinCtrl> r2("spinner"); @@ -52,6 +52,7 @@ LLSpinCtrl::Params::Params() : label_width("label_width"), decimal_digits("decimal_digits"), allow_text_entry("allow_text_entry", true), + label_wrap("label_wrap", false), text_enabled_color("text_enabled_color"), text_disabled_color("text_disabled_color"), up_button("up_button"), @@ -80,6 +81,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) { LLRect label_rect( 0, centered_top, label_width, centered_bottom ); LLTextBox::Params params; + params.wrap(p.label_wrap); params.name("SpinCtrl Label"); params.rect(label_rect); params.initial_value(p.label()); @@ -122,14 +124,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) params.max_length.bytes(MAX_STRING_LENGTH); params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2))); - if( mPrecision>0 )//should accept float numbers - { - params.prevalidate_callback(&LLTextValidate::validateFloat); - } - else //should accept int numbers - { - params.prevalidate_callback(&LLTextValidate::validateInt); - } + //*NOTE: allow entering of any chars for LLCalc, proper input will be evaluated on commit params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); mEditor = LLUICtrlFactory::create<LLLineEditor> (params); @@ -138,6 +133,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) // than when it doesn't. Instead, if you always have to double click to select all the text, // it's easier to understand //mEditor->setSelectAllonFocusReceived(TRUE); + mEditor->setSelectAllonCommit(FALSE); addChild(mEditor); updateEditor(); @@ -302,9 +298,10 @@ void LLSpinCtrl::onEditorCommit( const LLSD& data ) { BOOL success = FALSE; - std::string text = mEditor->getText(); - if( LLLineEditor::postvalidateFloat( text ) ) + if( mEditor->evaluateFloat() ) { + std::string text = mEditor->getText(); + LLLocale locale(LLLocale::USER_LOCALE); F32 val = (F32) atof(text.c_str()); @@ -325,7 +322,11 @@ void LLSpinCtrl::onEditorCommit( const LLSD& data ) } updateEditor(); - if( !success ) + if( success ) + { + updateEditor(); + } + else { reportInvalidData(); } diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h index 8960971594..87814f838e 100644 --- a/indra/llui/llspinctrl.h +++ b/indra/llui/llspinctrl.h @@ -44,6 +44,7 @@ public: Optional<S32> label_width; Optional<U32> decimal_digits; Optional<bool> allow_text_entry; + Optional<bool> label_wrap; Optional<LLUIColor> text_enabled_color; Optional<LLUIColor> text_disabled_color; @@ -95,6 +96,9 @@ public: void onUpBtn(const LLSD& data); void onDownBtn(const LLSD& data); + + const LLColor4& getEnabledTextColor() const { return mTextEnabledColor.get(); } + const LLColor4& getDisabledTextColor() const { return mTextDisabledColor.get(); } private: void updateLabelColor(); diff --git a/indra/llui/llstatbar.h b/indra/llui/llstatbar.h index 62a9db82fe..513fff3234 100644 --- a/indra/llui/llstatbar.h +++ b/indra/llui/llstatbar.h @@ -65,7 +65,7 @@ public: show_mean("show_mean", TRUE), stat("stat") { - follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT); + changeDefault(follows.flags, FOLLOWS_TOP | FOLLOWS_LEFT); } }; LLStatBar(const Params&); diff --git a/indra/llui/llstatview.h b/indra/llui/llstatview.h index 22a9fcd672..5abdc42448 100644 --- a/indra/llui/llstatview.h +++ b/indra/llui/llstatview.h @@ -46,7 +46,7 @@ public: Params() : setting("setting") { - follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT); + changeDefault(follows.flags, FOLLOWS_TOP | FOLLOWS_LEFT); } }; diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp index 28a064e6b6..bb731f4f7e 100644 --- a/indra/llui/llstyle.cpp +++ b/indra/llui/llstyle.cpp @@ -88,7 +88,7 @@ void LLStyle::setVisible(BOOL is_visible) mVisible = is_visible; } -LLUIImagePtr LLStyle::getImage() const +LLPointer<LLUIImage> LLStyle::getImage() const { return mImagep; } diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index 322edc343c..9f1eba79d8 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -30,6 +30,7 @@ #include "v4color.h" #include "llui.h" #include "llinitparam.h" +#include "lluiimage.h" class LLFontGL; @@ -72,7 +73,7 @@ public: void setLinkHREF(const std::string& href); BOOL isLink() const; - LLUIImagePtr getImage() const; + LLPointer<LLUIImage> getImage() const; void setImage(const LLUUID& src); void setImage(const std::string& name); @@ -108,7 +109,7 @@ private: const LLFontGL* mFont; std::string mLink; bool mIsLink; - LLUIImagePtr mImagep; + LLPointer<LLUIImage> mImagep; }; typedef LLPointer<LLStyle> LLStyleSP; diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index 7f0d650403..5fc2cc350d 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -98,24 +98,25 @@ class LLCustomButtonIconCtrl : public LLButton { public: struct Params - : public LLInitParam::Block<Params, LLButton::Params> + : public LLInitParam::Block<Params, LLButton::Params> { // LEFT, RIGHT, TOP, BOTTOM paddings of LLIconCtrl in this class has same value Optional<S32> icon_ctrl_pad; - Params(): - icon_ctrl_pad("icon_ctrl_pad", 1) + Params() + : icon_ctrl_pad("icon_ctrl_pad", 1) {} }; protected: friend class LLUICtrlFactory; - LLCustomButtonIconCtrl(const Params& p): - LLButton(p), + + LLCustomButtonIconCtrl(const Params& p) + : LLButton(p), mIcon(NULL), mIconAlignment(LLFontGL::HCENTER), mIconCtrlPad(p.icon_ctrl_pad) - {} + {} public: @@ -214,13 +215,11 @@ LLTabContainer::Params::Params() middle_tab("middle_tab"), last_tab("last_tab"), use_custom_icon_ctrl("use_custom_icon_ctrl", false), + open_tabs_on_drag_and_drop("open_tabs_on_drag_and_drop", false), tab_icon_ctrl_pad("tab_icon_ctrl_pad", 0), use_ellipses("use_ellipses"), font_halign("halign") -{ - name(std::string("tab_container")); - mouse_opaque = false; -} +{} LLTabContainer::LLTabContainer(const LLTabContainer::Params& p) : LLPanel(p), @@ -253,6 +252,7 @@ LLTabContainer::LLTabContainer(const LLTabContainer::Params& p) mMiddleTabParams(p.middle_tab), mLastTabParams(p.last_tab), mCustomIconCtrlUsed(p.use_custom_icon_ctrl), + mOpenTabsOnDragAndDrop(p.open_tabs_on_drag_and_drop), mTabIconCtrlPad(p.tab_icon_ctrl_pad), mUseTabEllipses(p.use_ellipses) { @@ -551,23 +551,23 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) } S32 tab_count = getTabCount(); - if (tab_count > 0) + if (tab_count > 0 && !getTabsHidden()) { LLTabTuple* firsttuple = getTab(0); LLRect tab_rect; if (mIsVertical) { tab_rect = LLRect(firsttuple->mButton->getRect().mLeft, - has_scroll_arrows ? mPrevArrowBtn->getRect().mBottom - tabcntrv_pad : mPrevArrowBtn->getRect().mTop, - firsttuple->mButton->getRect().mRight, - has_scroll_arrows ? mNextArrowBtn->getRect().mTop + tabcntrv_pad : mNextArrowBtn->getRect().mBottom ); + has_scroll_arrows ? mPrevArrowBtn->getRect().mBottom - tabcntrv_pad : mPrevArrowBtn->getRect().mTop, + firsttuple->mButton->getRect().mRight, + has_scroll_arrows ? mNextArrowBtn->getRect().mTop + tabcntrv_pad : mNextArrowBtn->getRect().mBottom ); } else { tab_rect = LLRect(has_scroll_arrows ? mPrevArrowBtn->getRect().mRight : mJumpPrevArrowBtn->getRect().mLeft, - firsttuple->mButton->getRect().mTop, - has_scroll_arrows ? mNextArrowBtn->getRect().mLeft : mJumpNextArrowBtn->getRect().mRight, - firsttuple->mButton->getRect().mBottom ); + firsttuple->mButton->getRect().mTop, + has_scroll_arrows ? mNextArrowBtn->getRect().mLeft : mJumpNextArrowBtn->getRect().mRight, + firsttuple->mButton->getRect().mBottom ); } if( tab_rect.pointInRect( x, y ) ) { @@ -684,7 +684,7 @@ BOOL LLTabContainer::handleToolTip( S32 x, S32 y, MASK mask) { static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0); BOOL handled = LLPanel::handleToolTip( x, y, mask); - if (!handled && getTabCount() > 0) + if (!handled && getTabCount() > 0 && !getTabsHidden()) { LLTabTuple* firsttuple = getTab(0); @@ -815,48 +815,62 @@ BOOL LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDrag { BOOL has_scroll_arrows = (getMaxScrollPos() > 0); - if( mDragAndDropDelayTimer.getStarted() && mDragAndDropDelayTimer.getElapsedTimeF32() > SCROLL_DELAY_TIME ) + if(mOpenTabsOnDragAndDrop && !getTabsHidden()) { - if (has_scroll_arrows) + // In that case, we'll open the hovered tab while dragging and dropping items. + // This allows for drilling through tabs. + if (mDragAndDropDelayTimer.getStarted()) { - if (mJumpPrevArrowBtn && mJumpPrevArrowBtn->getRect().pointInRect(x, y)) - { - S32 local_x = x - mJumpPrevArrowBtn->getRect().mLeft; - S32 local_y = y - mJumpPrevArrowBtn->getRect().mBottom; - mJumpPrevArrowBtn->handleHover(local_x, local_y, mask); - } - if (mJumpNextArrowBtn && mJumpNextArrowBtn->getRect().pointInRect(x, y)) + if (mDragAndDropDelayTimer.getElapsedTimeF32() > SCROLL_DELAY_TIME) { - S32 local_x = x - mJumpNextArrowBtn->getRect().mLeft; - S32 local_y = y - mJumpNextArrowBtn->getRect().mBottom; - mJumpNextArrowBtn->handleHover(local_x, local_y, mask); - } - if (mPrevArrowBtn->getRect().pointInRect(x, y)) - { - S32 local_x = x - mPrevArrowBtn->getRect().mLeft; - S32 local_y = y - mPrevArrowBtn->getRect().mBottom; - mPrevArrowBtn->handleHover(local_x, local_y, mask); - } - else if (mNextArrowBtn->getRect().pointInRect(x, y)) - { - S32 local_x = x - mNextArrowBtn->getRect().mLeft; - S32 local_y = y - mNextArrowBtn->getRect().mBottom; - mNextArrowBtn->handleHover(local_x, local_y, mask); - } - } + if (has_scroll_arrows) + { + if (mJumpPrevArrowBtn && mJumpPrevArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mJumpPrevArrowBtn->getRect().mLeft; + S32 local_y = y - mJumpPrevArrowBtn->getRect().mBottom; + mJumpPrevArrowBtn->handleHover(local_x, local_y, mask); + } + if (mJumpNextArrowBtn && mJumpNextArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mJumpNextArrowBtn->getRect().mLeft; + S32 local_y = y - mJumpNextArrowBtn->getRect().mBottom; + mJumpNextArrowBtn->handleHover(local_x, local_y, mask); + } + if (mPrevArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mPrevArrowBtn->getRect().mLeft; + S32 local_y = y - mPrevArrowBtn->getRect().mBottom; + mPrevArrowBtn->handleHover(local_x, local_y, mask); + } + else if (mNextArrowBtn->getRect().pointInRect(x, y)) + { + S32 local_x = x - mNextArrowBtn->getRect().mLeft; + S32 local_y = y - mNextArrowBtn->getRect().mBottom; + mNextArrowBtn->handleHover(local_x, local_y, mask); + } + } - for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) - { - LLTabTuple* tuple = *iter; - tuple->mButton->setVisible( TRUE ); - S32 local_x = x - tuple->mButton->getRect().mLeft; - S32 local_y = y - tuple->mButton->getRect().mBottom; - if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible()) - { - tuple->mButton->onCommit(); + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( TRUE ); + S32 local_x = x - tuple->mButton->getRect().mLeft; + S32 local_y = y - tuple->mButton->getRect().mBottom; + if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible()) + { + tuple->mButton->onCommit(); + } + } + // Stop the timer whether successful or not. Don't let it run forever. mDragAndDropDelayTimer.stop(); } } + else + { + // Start a timer so we don't open tabs as soon as we hover on them + mDragAndDropDelayTimer.start(); + } } return LLView::handleDragAndDrop(x, y, mask, drop, type, cargo_data, accept, tooltip); @@ -1026,85 +1040,50 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) } else { + LLButton::Params& p = (mCustomIconCtrlUsed ? custom_btn_params : normal_btn_params); + + p.rect(btn_rect); + p.font(mFont); + p.font_halign = mFontHalign; + p.label(trimmed_label); + p.click_callback.function(boost::bind(&LLTabContainer::onTabBtn, this, _2, child)); + if (indent) + { + p.pad_left(indent); + } + p.pad_bottom( mLabelPadBottom ); + p.scale_image(true); + p.tab_stop(false); + p.label_shadow(false); + p.follows.flags = FOLLOWS_LEFT; + if (mIsVertical) { - LLButton::Params& p = (mCustomIconCtrlUsed)? - custom_btn_params:normal_btn_params; - p.name(std::string("vert tab button")); - p.rect(btn_rect); - p.follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT); - p.click_callback.function(boost::bind(&LLTabContainer::onTabBtn, this, _2, child)); - p.font(mFont); - p.label(trimmed_label); p.image_unselected(mMiddleTabParams.tab_left_image_unselected); p.image_selected(mMiddleTabParams.tab_left_image_selected); - p.scale_image(true); - p.font_halign = mFontHalign; - p.pad_bottom( mLabelPadBottom ); - p.tab_stop(false); - p.label_shadow(false); - if (indent) - { - p.pad_left(indent); - } - - - if(mCustomIconCtrlUsed) - { - btn = LLUICtrlFactory::create<LLCustomButtonIconCtrl>(custom_btn_params); - - } - else - { - btn = LLUICtrlFactory::create<LLButton>(p); - } + p.follows.flags = p.follows.flags() | FOLLOWS_TOP; } else { - LLButton::Params& p = (mCustomIconCtrlUsed)? - custom_btn_params:normal_btn_params; p.name(std::string(child->getName()) + " tab"); - p.rect(btn_rect); - p.click_callback.function(boost::bind(&LLTabContainer::onTabBtn, this, _2, child)); - p.font(mFont); - p.label(trimmed_label); p.visible(false); - p.scale_image(true); p.image_unselected(tab_img); p.image_selected(tab_selected_img); - p.tab_stop(false); - p.label_shadow(false); + p.follows.flags = p.follows.flags() | (getTabPosition() == TOP ? FOLLOWS_TOP : FOLLOWS_BOTTOM); // Try to squeeze in a bit more text p.pad_left( mLabelPadLeft ); p.pad_right(2); - p.pad_bottom( mLabelPadBottom ); - p.font_halign = mFontHalign; - p.follows.flags = FOLLOWS_LEFT; - p.follows.flags = FOLLOWS_LEFT; - - if (indent) - { - p.pad_left(indent); - } - - if( getTabPosition() == TOP ) - { - p.follows.flags = p.follows.flags() | FOLLOWS_TOP; - } - else - { - p.follows.flags = p.follows.flags() | FOLLOWS_BOTTOM; - } - - if(mCustomIconCtrlUsed) - { - btn = LLUICtrlFactory::create<LLCustomButtonIconCtrl>(custom_btn_params); - } - else - { - btn = LLUICtrlFactory::create<LLButton>(p); - } + } + + // *TODO : It seems wrong not to use p in both cases considering the way p is initialized + if (mCustomIconCtrlUsed) + { + btn = LLUICtrlFactory::create<LLCustomButtonIconCtrl>(custom_btn_params); + } + else + { + btn = LLUICtrlFactory::create<LLButton>(p); } } @@ -1281,6 +1260,10 @@ void LLTabContainer::enableTabButton(S32 which, BOOL enable) { mTabList[which]->mButton->setEnabled(enable); } + // Stop the DaD timer as it might run forever + // enableTabButton() is typically called on refresh and draw when anything changed + // in the tab container so it's a good time to reset that. + mDragAndDropDelayTimer.stop(); } void LLTabContainer::deleteAllTabs() diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index eaa2fd54e0..cebace2ceb 100644 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -105,6 +105,11 @@ public: Optional<bool> use_custom_icon_ctrl; /** + * Open tabs on hover in drag and drop situations + */ + Optional<bool> open_tabs_on_drag_and_drop; + + /** * Paddings for LLIconCtrl in case of LLCustomButtonIconCtrl usage(use_custom_icon_ctrl = true) */ Optional<S32> tab_icon_ctrl_pad; @@ -300,6 +305,7 @@ private: TabParams mLastTabParams; bool mCustomIconCtrlUsed; + bool mOpenTabsOnDragAndDrop; S32 mTabIconCtrlPad; bool mUseTabEllipses; }; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 3f213ed13e..7aeeae298f 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -157,6 +157,7 @@ LLTextBase::Params::Params() read_only("read_only", false), v_pad("v_pad", 0), h_pad("h_pad", 0), + clip("clip", true), clip_partial("clip_partial", true), line_spacing("line_spacing"), max_text_length("max_length", 255), @@ -199,6 +200,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mVAlign(p.font_valign), mLineSpacingMult(p.line_spacing.multiple), mLineSpacingPixels(p.line_spacing.pixels), + mClip(p.clip), mClipPartial(p.clip_partial && !p.allow_scroll), mTrackEnd( p.track_end ), mScrollIndex(-1), @@ -278,7 +280,7 @@ bool LLTextBase::truncate() if (getLength() >= S32(mMaxTextByteLength / 4)) { // Have to check actual byte size - LLWString text(getWText()); + LLWString text(getWText()); S32 utf8_byte_size = wstring_utf8_length(text); if ( utf8_byte_size > mMaxTextByteLength ) { @@ -334,7 +336,7 @@ void LLTextBase::drawSelectionBackground() // binary search for line that starts before top of visible buffer line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom()); - line_list_t::const_iterator end_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top()); + line_list_t::const_iterator end_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top()); bool done = false; @@ -512,7 +514,6 @@ void LLTextBase::drawText() selection_right = llmax( mSelectionStart, mSelectionEnd ); } - LLRect scrolled_view_rect = getVisibleDocumentRect(); std::pair<S32, S32> line_range = getVisibleLines(mClipPartial); S32 first_line = line_range.first; S32 last_line = line_range.second; @@ -545,10 +546,9 @@ void LLTextBase::drawText() line_end = next_start; } - LLRect text_rect(line.mRect.mLeft + mVisibleTextRect.mLeft - scrolled_view_rect.mLeft, - line.mRect.mTop - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom, - llmin(mDocumentView->getRect().getWidth(), line.mRect.mRight) - scrolled_view_rect.mLeft, - line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom); + LLRect text_rect(line.mRect); + text_rect.mRight = mDocumentView->getRect().getWidth(); // clamp right edge to document extents + text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); // adjust by scroll position // draw a single line of text S32 seg_start = line_start; @@ -598,7 +598,7 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s pos = getEditableIndex(pos, true); - segment_set_t::iterator seg_iter = getSegIterContaining(pos); + segment_set_t::iterator seg_iter = getEditableSegIterContaining(pos); LLTextSegmentPtr default_segment; @@ -654,7 +654,7 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s } text.insert(pos, wstr); - getViewModel()->setDisplay(text); + getViewModel()->setDisplay(text); if ( truncate() ) { @@ -669,7 +669,7 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) { - LLWString text(getWText()); + LLWString text(getWText()); segment_set_t::iterator seg_iter = getSegIterContaining(pos); while(seg_iter != mSegments.end()) { @@ -716,7 +716,7 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) } text.erase(pos, length); - getViewModel()->setDisplay(text); + getViewModel()->setDisplay(text); // recreate default segment in case we erased everything createDefaultSegment(); @@ -733,9 +733,9 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) { return 0; } - LLWString text(getWText()); + LLWString text(getWText()); text[pos] = wc; - getViewModel()->setDisplay(text); + getViewModel()->setDisplay(text); onValueChange(pos, pos + 1); needsReflow(pos); @@ -856,7 +856,7 @@ BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) // Did we just click on a link? if (mURLClickSignal && cur_segment->getStyle() - && cur_segment->getStyle()->isLink()) + && cur_segment->getStyle()->isLink()) { // *TODO: send URL here? (*mURLClickSignal)(this, LLSD() ); @@ -993,44 +993,68 @@ void LLTextBase::draw() updateScrollFromCursor(); } - LLRect doc_rect; + LLRect text_rect; if (mScroller) { - mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &doc_rect, this); + mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &text_rect, this); } else { - doc_rect = getLocalRect(); + LLRect visible_lines_rect; + std::pair<S32, S32> line_range = getVisibleLines(mClipPartial); + for (S32 i = line_range.first; i < line_range.second; i++) + { + if (visible_lines_rect.isEmpty()) + { + visible_lines_rect = mLineInfoList[i].mRect; + } + else + { + visible_lines_rect.unionWith(mLineInfoList[i].mRect); + } + } + text_rect = visible_lines_rect; + text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); } if (mBGVisible) { + F32 alpha = getCurrentTransparency(); // clip background rect against extents, if we support scrolling LLRect bg_rect = mVisibleTextRect; if (mScroller) { - bg_rect.intersectWith(doc_rect); + bg_rect.intersectWith(text_rect); } LLColor4 bg_color = mReadOnly ? mReadOnlyBgColor.get() : hasFocus() ? mFocusBgColor.get() : mWriteableBgColor.get(); - gl_rect_2d(doc_rect, bg_color, TRUE); + gl_rect_2d(text_rect, bg_color % alpha, TRUE); } - // draw document view - LLUICtrl::draw(); - - { - // only clip if we support scrolling... - // since convention is that text boxes never vertically truncate their contents - // regardless of rect bounds - LLLocalClipRect clip(doc_rect, mScroller != NULL); + bool should_clip = mClip || mScroller != NULL; + { LLLocalClipRect clip(text_rect, should_clip); + + // draw document view + if (mScroller) + { + drawChild(mScroller); + } + else + { + drawChild(mDocumentView); + } + drawSelectionBackground(); drawText(); drawCursor(); } + + mDocumentView->setVisible(FALSE); + LLUICtrl::draw(); + mDocumentView->setVisible(TRUE); } @@ -1094,8 +1118,7 @@ void LLTextBase::updateScrollFromCursor() // scroll so that the cursor is at the top of the page LLRect scroller_doc_window = getVisibleDocumentRect(); - LLRect cursor_rect_doc = getLocalRectFromDocIndex(mCursorPos); - cursor_rect_doc.translate(scroller_doc_window.mLeft, scroller_doc_window.mBottom); + LLRect cursor_rect_doc = getDocRectFromDocIndex(mCursorPos); mScroller->scrollToShowRect(cursor_rect_doc, LLRect(0, scroller_doc_window.getHeight() - 5, scroller_doc_window.getWidth(), 5)); } @@ -1139,7 +1162,7 @@ void LLTextBase::reflow() S32 first_line = getFirstVisibleLine(); // if scroll anchor not on first line, update it to first character of first line - if (!mLineInfoList.empty() + if ((first_line < mLineInfoList.size()) && (mScrollIndex < mLineInfoList[first_line].mDocIndexStart || mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd)) { @@ -1169,7 +1192,10 @@ void LLTextBase::reflow() // shrink document to minimum size (visible portion of text widget) // to force inlined widgets with follows set to shrink - mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight()); + if (mWordWrap) + { + mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight()); + } S32 cur_top = 0; @@ -1341,9 +1367,9 @@ S32 LLTextBase::getLineStart( S32 line ) const { S32 num_lines = getLineCount(); if (num_lines == 0) - { + { return 0; - } + } line = llclamp(line, 0, num_lines-1); return mLineInfoList[line].mDocIndexStart; @@ -1353,9 +1379,9 @@ S32 LLTextBase::getLineEnd( S32 line ) const { S32 num_lines = getLineCount(); if (num_lines == 0) - { + { return 0; - } + } line = llclamp(line, 0, num_lines-1); return mLineInfoList[line].mDocIndexEnd; @@ -1414,7 +1440,7 @@ S32 LLTextBase::getFirstVisibleLine() const return iter - mLineInfoList.begin(); } -std::pair<S32, S32> LLTextBase::getVisibleLines(bool fully_visible) +std::pair<S32, S32> LLTextBase::getVisibleLines(bool require_fully_visible) { LLRect visible_region = getVisibleDocumentRect(); line_list_t::const_iterator first_iter; @@ -1423,14 +1449,14 @@ std::pair<S32, S32> LLTextBase::getVisibleLines(bool fully_visible) // make sure we have an up-to-date mLineInfoList reflow(); - if (fully_visible) + if (require_fully_visible) { first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top()); - last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom()); + last_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom()); } else { - first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); + first_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top()); } return std::pair<S32, S32>(first_iter - mLineInfoList.begin(), last_iter - mLineInfoList.begin()); @@ -1487,8 +1513,48 @@ void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg } } +LLTextBase::segment_set_t::iterator LLTextBase::getEditableSegIterContaining(S32 index) +{ + segment_set_t::iterator it = getSegIterContaining(index); + segment_set_t::iterator orig_it = it; + + if (it == mSegments.end()) return it; + + if (!(*it)->canEdit() + && index == (*it)->getStart() + && it != mSegments.begin()) + { + it--; + if ((*it)->canEdit()) + { + return it; + } + } + return orig_it; +} + +LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaining(S32 index) const +{ + segment_set_t::const_iterator it = getSegIterContaining(index); + segment_set_t::const_iterator orig_it = it; + if (it == mSegments.end()) return it; + + if (!(*it)->canEdit() + && index == (*it)->getStart() + && it != mSegments.begin()) + { + it--; + if ((*it)->canEdit()) + { + return it; + } + } + return orig_it; +} + LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) { + static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment(); if (index > getLength()) { return mSegments.end(); } @@ -1631,8 +1697,11 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para LLUrlMatch match; std::string text = new_text; while ( LLUrlRegistry::instance().findUrl(text, match, - boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3)) ) + boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3)) ) { + + LLTextUtil::processUrlMatch(&match,this); + start = match.getStart(); end = match.getEnd()+1; @@ -1654,10 +1723,6 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para std::string subtext=text.substr(0,start); appendAndHighlightText(subtext, part, style_params); } - - // inserts an avatar icon preceding the Url if appropriate - LLTextUtil::processUrlMatch(&match,this); - // output the styled Url appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); @@ -1925,7 +1990,7 @@ void LLTextBase::setWText(const LLWString& text) const LLWString& LLTextBase::getWText() const { - return getViewModel()->getDisplay(); + return getViewModel()->getDisplay(); } // If round is true, if the position is on the right half of a character, the cursor @@ -1936,9 +2001,12 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, { // Figure out which line we're nearest to. LLRect visible_region = getVisibleDocumentRect(); + LLRect doc_rect = mDocumentView->getRect(); + + S32 doc_y = local_y - doc_rect.mBottom; // binary search for line that starts before local_y - line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), local_y - mVisibleTextRect.mBottom + visible_region.mBottom, compare_bottom()); + line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_y, compare_bottom()); if (line_iter == mLineInfoList.end()) { @@ -1946,7 +2014,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, } S32 pos = getLength(); - S32 start_x = mVisibleTextRect.mLeft + line_iter->mRect.mLeft - visible_region.mLeft; + S32 start_x = line_iter->mRect.mLeft + doc_rect.mLeft; segment_set_t::iterator line_seg_iter; S32 line_seg_offset; @@ -1968,7 +2036,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, } // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line - if (hit_past_end_of_line && local_y - mVisibleTextRect.mBottom + visible_region.mBottom > line_iter->mRect.mTop) + if (hit_past_end_of_line && doc_y > line_iter->mRect.mTop) { pos = segment_line_start; break; @@ -1997,11 +2065,19 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, pos = segment_line_start + offset; break; } - else if (hit_past_end_of_line && segmentp->getEnd() > line_iter->mDocIndexEnd - 1) + else if (hit_past_end_of_line && segmentp->getEnd() >= line_iter->mDocIndexEnd) { - // segment wraps to next line, so just set doc pos to the end of the line - // segment wraps to next line, so just set doc pos to start of next line (represented by mDocIndexEnd) - pos = llmin(getLength(), line_iter->mDocIndexEnd); + if (getLineNumFromDocIndex(line_iter->mDocIndexEnd - 1) == line_iter->mLineNum) + { + // if segment wraps to the next line we should step one char back + // to compensate for the space char between words + // which is removed due to wrapping + pos = llclamp(line_iter->mDocIndexEnd - 1, 0, getLength()); + } + else + { + pos = llclamp(line_iter->mDocIndexEnd, 0, getLength()); + } break; } start_x += text_width; @@ -2084,7 +2160,7 @@ LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const { // return default height rect in upper left local_rect = content_window_rect; - local_rect.mBottom = local_rect.mTop - (S32)(mDefaultFont->getLineHeight()); + local_rect.mBottom = local_rect.mTop - mDefaultFont->getLineHeight(); return local_rect; } @@ -2307,6 +2383,9 @@ S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction) void LLTextBase::updateRects() { + LLRect old_text_rect = mVisibleTextRect; + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + if (mLineInfoList.empty()) { mTextBoundingRect = LLRect(0, mVPad, mHPad, 0); @@ -2322,10 +2401,24 @@ void LLTextBase::updateRects() } mTextBoundingRect.mTop += mVPad; - // subtract a pixel off the bottom to deal with rounding errors in measuring font height - mTextBoundingRect.mBottom -= 1; - S32 delta_pos = -mTextBoundingRect.mBottom; + S32 delta_pos = 0; + + switch(mVAlign) + { + case LLFontGL::TOP: + delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom); + break; + case LLFontGL::VCENTER: + delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2; + break; + case LLFontGL::BOTTOM: + delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom; + break; + case LLFontGL::BASELINE: + // do nothing + break; + } // move line segments to fit new document rect for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) { @@ -2335,8 +2428,9 @@ void LLTextBase::updateRects() } // update document container dimensions according to text contents - LLRect doc_rect = mTextBoundingRect; + LLRect doc_rect; // use old mVisibleTextRect constraint document to width of viewable region + doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom); doc_rect.mLeft = 0; // allow horizontal scrolling? @@ -2346,11 +2440,22 @@ void LLTextBase::updateRects() doc_rect.mRight = mScroller ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) : mVisibleTextRect.getWidth(); + doc_rect.mTop = llmax(mVisibleTextRect.mTop, mTextBoundingRect.mTop); if (!mScroller) { // push doc rect to top of text widget - doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); + switch(mVAlign) + { + case LLFontGL::TOP: + doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); + break; + case LLFontGL::VCENTER: + doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2); + case LLFontGL::BOTTOM: + default: + break; + } } mDocumentView->setShape(doc_rect); @@ -2358,7 +2463,6 @@ void LLTextBase::updateRects() //update mVisibleTextRect *after* mDocumentView has been resized // so that scrollbars are added if document needs to scroll // since mVisibleTextRect does not include scrollbars - LLRect old_text_rect = mVisibleTextRect; mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); //FIXME: replace border with image? if (mBorderVisible) @@ -2371,9 +2475,27 @@ void LLTextBase::updateRects() } // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed) + doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom); + doc_rect.mLeft = 0; doc_rect.mRight = mScroller ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) : mVisibleTextRect.getWidth(); + doc_rect.mTop = llmax(mVisibleTextRect.mTop, mTextBoundingRect.mTop); + if (!mScroller) + { + // push doc rect to top of text widget + switch(mVAlign) + { + case LLFontGL::TOP: + doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); + break; + case LLFontGL::VCENTER: + doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2); + case LLFontGL::BOTTOM: + default: + break; + } + } mDocumentView->setShape(doc_rect); } @@ -2404,10 +2526,37 @@ LLRect LLTextBase::getVisibleDocumentRect() const { return mScroller->getVisibleContentRect(); } - else + else if (mClip) { - // entire document rect is visible when not scrolling + LLRect visible_text_rect = getVisibleTextRect(); + LLRect doc_rect = mDocumentView->getRect(); + visible_text_rect.translate(-doc_rect.mLeft, -doc_rect.mBottom); + + // reject partially visible lines + LLRect visible_lines_rect; + for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end(); + it != end_it; + ++it) + { + bool line_visible = mClipPartial ? visible_text_rect.contains(it->mRect) : visible_text_rect.overlaps(it->mRect); + if (line_visible) + { + if (visible_lines_rect.isEmpty()) + { + visible_lines_rect = it->mRect; + } + else + { + visible_lines_rect.unionWith(it->mRect); + } + } + } + return visible_lines_rect; + } + else + { // entire document rect is visible // but offset according to height of widget + LLRect doc_rect = mDocumentView->getLocalRect(); doc_rect.mLeft -= mDocumentView->getRect().mLeft; // adjust for height of text above widget baseline @@ -2458,7 +2607,10 @@ BOOL LLTextSegment::handleDoubleClick(S32 x, S32 y, MASK mask) { return FALSE; } BOOL LLTextSegment::handleHover(S32 x, S32 y, MASK mask) { return FALSE; } BOOL LLTextSegment::handleScrollWheel(S32 x, S32 y, S32 clicks) { return FALSE; } BOOL LLTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { return FALSE; } -std::string LLTextSegment::getName() const { return ""; } +const std::string& LLTextSegment::getName() const +{ + return LLStringUtil::null; +} void LLTextSegment::onMouseCaptureLost() {} void LLTextSegment::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {} void LLTextSegment::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {} @@ -2474,7 +2626,7 @@ LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 e mToken(NULL), mEditor(editor) { - mFontHeight = llceil(mStyle->getFont()->getLineHeight()); + mFontHeight = mStyle->getFont()->getLineHeight(); LLUIImagePtr image = mStyle->getImage(); if (image.notNull()) @@ -2490,7 +2642,7 @@ LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 { mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color)); - mFontHeight = llceil(mStyle->getFont()->getLineHeight()); + mFontHeight = mStyle->getFont()->getLineHeight(); } LLNormalTextSegment::~LLNormalTextSegment() @@ -2525,21 +2677,21 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha; - if( selection_start > seg_start ) + if( selection_start > seg_start ) { // Draw normally S32 start = seg_start; S32 end = llmin( selection_start, seg_end ); S32 length = end - start; font->render(text, start, - rect, - color, - LLFontGL::LEFT, mEditor.mVAlign, - LLFontGL::NORMAL, - mStyle->getShadowType(), - length, - &right_x, - mEditor.getUseEllipses()); + rect, + color, + LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, + &right_x, + mEditor.getUseEllipses()); } rect.mLeft = (S32)ceil(right_x); @@ -2551,14 +2703,14 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele S32 length = end - start; font->render(text, start, - rect, - mStyle->getSelectedColor().get(), - LLFontGL::LEFT, mEditor.mVAlign, - LLFontGL::NORMAL, - LLFontGL::NO_SHADOW, - length, - &right_x, - mEditor.getUseEllipses()); + rect, + mStyle->getSelectedColor().get(), + LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::NORMAL, + LLFontGL::NO_SHADOW, + length, + &right_x, + mEditor.getUseEllipses()); } rect.mLeft = (S32)ceil(right_x); if( selection_end < seg_end ) @@ -2568,14 +2720,14 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele S32 end = seg_end; S32 length = end - start; font->render(text, start, - rect, - color, - LLFontGL::LEFT, mEditor.mVAlign, - LLFontGL::NORMAL, - mStyle->getShadowType(), - length, - &right_x, - mEditor.getUseEllipses()); + rect, + color, + LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, + &right_x, + mEditor.getUseEllipses()); } return right_x; } @@ -2858,11 +3010,11 @@ LLLineBreakTextSegment::LLLineBreakTextSegment(S32 pos):LLTextSegment(pos,pos+1) { LLStyleSP s( new LLStyle(LLStyle::Params().visible(true))); - mFontHeight = llceil(s->getFont()->getLineHeight()); + mFontHeight = s->getFont()->getLineHeight(); } LLLineBreakTextSegment::LLLineBreakTextSegment(LLStyleConstSP style,S32 pos):LLTextSegment(pos,pos+1) { - mFontHeight = llceil(style->getFont()->getLineHeight()); + mFontHeight = style->getFont()->getLineHeight(); } LLLineBreakTextSegment::~LLLineBreakTextSegment() { @@ -2899,7 +3051,7 @@ static const S32 IMAGE_HPAD = 3; bool LLImageTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { width = 0; - height = llceil(mStyle->getFont()->getLineHeight());; + height = mStyle->getFont()->getLineHeight(); LLUIImagePtr image = mStyle->getImage(); if( num_chars>0 && image.notNull()) @@ -2913,11 +3065,18 @@ bool LLImageTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width S32 LLImageTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { LLUIImagePtr image = mStyle->getImage(); + + if (image.isNull()) + { + return 1; + } + S32 image_width = image->getWidth(); if(line_offset == 0 || num_pixels>image_width + IMAGE_HPAD) { return 1; } + return 0; } @@ -2927,18 +3086,21 @@ F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 select { LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; LLUIImagePtr image = mStyle->getImage(); - S32 style_image_height = image->getHeight(); - S32 style_image_width = image->getWidth(); - // Text is drawn from the top of the draw_rect downward - - S32 text_center = draw_rect.mTop - (draw_rect.getHeight() / 2); - // Align image to center of draw rect - S32 image_bottom = text_center - (style_image_height / 2); - image->draw(draw_rect.mLeft, image_bottom, - style_image_width, style_image_height, color); - - const S32 IMAGE_HPAD = 3; - return draw_rect.mLeft + style_image_width + IMAGE_HPAD; + if (image.notNull()) + { + S32 style_image_height = image->getHeight(); + S32 style_image_width = image->getWidth(); + // Text is drawn from the top of the draw_rect downward + + S32 text_center = draw_rect.mTop - (draw_rect.getHeight() / 2); + // Align image to center of draw rect + S32 image_bottom = text_center - (style_image_height / 2); + image->draw(draw_rect.mLeft, image_bottom, + style_image_width, style_image_height, color); + + const S32 IMAGE_HPAD = 3; + return draw_rect.mLeft + style_image_width + IMAGE_HPAD; + } } return 0.0; } diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index aafcf8ceb0..0549141b72 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -84,7 +84,7 @@ public: /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ std::string getName() const; + /*virtual*/ const std::string& getName() const; /*virtual*/ void onMouseCaptureLost(); /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const; /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const; @@ -237,7 +237,7 @@ public: friend class LLNormalTextSegment; friend class LLUICtrlFactory; - struct LineSpacingParams : public LLInitParam::Choice<LineSpacingParams> + struct LineSpacingParams : public LLInitParam::ChoiceBlock<LineSpacingParams> { Alternative<F32> multiple; Alternative<S32> pixels; @@ -265,6 +265,7 @@ public: use_ellipses, parse_urls, parse_highlights, + clip, clip_partial; Optional<S32> v_pad, @@ -338,7 +339,7 @@ public: void addDocumentChild(LLView* view); void removeDocumentChild(LLView* view); const LLView* getDocumentView() const { return mDocumentView; } - LLRect getVisibleTextRect() { return mVisibleTextRect; } + LLRect getVisibleTextRect() const { return mVisibleTextRect; } LLRect getTextBoundingRect(); LLRect getVisibleDocumentRect() const; @@ -460,6 +461,8 @@ protected: void getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const; void getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ); LLTextSegmentPtr getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line = true); + segment_set_t::iterator getEditableSegIterContaining(S32 index); + segment_set_t::const_iterator getEditableSegIterContaining(S32 index) const; segment_set_t::iterator getSegIterContaining(S32 index); segment_set_t::const_iterator getSegIterContaining(S32 index) const; void clearSegments(); @@ -552,6 +555,7 @@ protected: bool mTrackEnd; // if true, keeps scroll position at end of document during resize bool mReadOnly; bool mBGVisible; // render background? + bool mClip; // clip text to widget rect bool mClipPartial; // false if we show lines that are partially inside bounding rect bool mPlainText; // didn't use Image or Icon segments S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 94bf716e7d..9720dded6c 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -277,6 +277,8 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : mHPad += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; updateRects(); } + + mParseOnTheFly = TRUE; } void LLTextEditor::initFromParams( const LLTextEditor::Params& p) @@ -324,8 +326,10 @@ void LLTextEditor::setText(const LLStringExplicit &utf8str, const LLStyle::Param blockUndo(); deselect(); - + + mParseOnTheFly = FALSE; LLTextBase::setText(utf8str, input_params); + mParseOnTheFly = TRUE; resetDirty(); } @@ -588,6 +592,10 @@ void LLTextEditor::indentSelectedLines( S32 spaces ) } } + // Disabling parsing on the fly to avoid updating text segments + // until all indentation commands are executed. + mParseOnTheFly = FALSE; + // Find each start-of-line and indent it do { @@ -613,6 +621,8 @@ void LLTextEditor::indentSelectedLines( S32 spaces ) } while( cur < right ); + mParseOnTheFly = TRUE; + if( (right < getLength()) && (text[right] == '\n') ) { right++; @@ -1322,7 +1332,7 @@ void LLTextEditor::cut() } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromSubstring( getWText(), left_pos, length, mSourceID ); + LLClipboard::instance().copyToClipboard( getWText(), left_pos, length); deleteSelection( FALSE ); onKeyStroke(); @@ -1342,12 +1352,12 @@ void LLTextEditor::copy() } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromSubstring(getWText(), left_pos, length, mSourceID); + LLClipboard::instance().copyToClipboard(getWText(), left_pos, length); } BOOL LLTextEditor::canPaste() const { - return !mReadOnly && gClipboard.canPasteString(); + return !mReadOnly && LLClipboard::instance().isTextAvailable(); } // paste from clipboard @@ -1367,6 +1377,7 @@ void LLTextEditor::pastePrimary() // paste from primary (itsprimary==true) or clipboard (itsprimary==false) void LLTextEditor::pasteHelper(bool is_primary) { + mParseOnTheFly = FALSE; bool can_paste_it; if (is_primary) { @@ -1382,16 +1393,8 @@ void LLTextEditor::pasteHelper(bool is_primary) return; } - LLUUID source_id; LLWString paste; - if (is_primary) - { - paste = gClipboard.getPastePrimaryWString(&source_id); - } - else - { - paste = gClipboard.getPasteWString(&source_id); - } + LLClipboard::instance().pasteFromClipboard(paste, is_primary); if (paste.empty()) { @@ -1450,6 +1453,7 @@ void LLTextEditor::pasteHelper(bool is_primary) deselect(); onKeyStroke(); + mParseOnTheFly = TRUE; } @@ -1463,12 +1467,12 @@ void LLTextEditor::copyPrimary() } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromPrimarySubstring(getWText(), left_pos, length, mSourceID); + LLClipboard::instance().copyToClipboard(getWText(), left_pos, length, true); } BOOL LLTextEditor::canPastePrimary() const { - return !mReadOnly && gClipboard.canPastePrimaryString(); + return !mReadOnly && LLClipboard::instance().isTextAvailable(true); } void LLTextEditor::updatePrimary() @@ -1980,7 +1984,7 @@ void LLTextEditor::drawPreeditMarker() return; } - const S32 line_height = llround( mDefaultFont->getLineHeight() ); + const S32 line_height = mDefaultFont->getLineHeight(); S32 line_start = getLineStart(cur_line); S32 line_y = mVisibleTextRect.mTop - line_height; @@ -2238,6 +2242,22 @@ void LLTextEditor::insertText(const std::string &new_text) setEnabled( enabled ); } +void LLTextEditor::insertText(LLWString &new_text) +{ + BOOL enabled = getEnabled(); + setEnabled( TRUE ); + + // Delete any selected characters (the insertion replaces them) + if( hasSelection() ) + { + deleteSelection(TRUE); + } + + setCursorPos(mCursorPos + insert( mCursorPos, new_text, FALSE, LLTextSegmentPtr() )); + + setEnabled( enabled ); +} + void LLTextEditor::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) { // Save old state @@ -2385,7 +2405,7 @@ void LLTextEditor::loadKeywords(const std::string& filename, void LLTextEditor::updateSegments() { - if (mReflowIndex < S32_MAX && mKeywords.isLoaded()) + if (mReflowIndex < S32_MAX && mKeywords.isLoaded() && mParseOnTheFly) { LLFastTimer ft(FTM_SYNTAX_HIGHLIGHTING); // HACK: No non-ascii keywords for now @@ -2687,7 +2707,7 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect const LLWString textString(getWText()); const llwchar * const text = textString.c_str(); - const S32 line_height = llround(mDefaultFont->getLineHeight()); + const S32 line_height = mDefaultFont->getLineHeight(); if (coord) { @@ -2790,7 +2810,7 @@ void LLTextEditor::markAsPreedit(S32 position, S32 length) S32 LLTextEditor::getPreeditFontSize() const { - return llround(mDefaultFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); + return llround((F32)mDefaultFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); } BOOL LLTextEditor::isDirty() const @@ -2826,3 +2846,13 @@ void LLTextEditor::clear() getViewModel()->setDisplay(LLWStringUtil::null); clearSegments(); } + +bool LLTextEditor::canLoadOrSaveToFile() +{ + return !mReadOnly; +} + +S32 LLTextEditor::spacesPerTab() +{ + return SPACES_PER_TAB; +} diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 58ecefdccb..40821ae9fb 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -92,6 +92,8 @@ public: void setParseHighlights(BOOL parsing) {mParseHighlights=parsing;} + static S32 spacesPerTab(); + // mousehandler overrides virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); @@ -140,6 +142,8 @@ public: virtual void selectAll(); virtual BOOL canSelectAll() const; + virtual bool canLoadOrSaveToFile(); + void selectNext(const std::string& search_text_in, BOOL case_insensitive, BOOL wrap = TRUE); BOOL replaceText(const std::string& search_text, const std::string& replace_text, BOOL case_insensitive, BOOL wrap = TRUE); void replaceTextAll(const std::string& search_text, const std::string& replace_text, BOOL case_insensitive); @@ -158,6 +162,7 @@ public: // inserts text at cursor void insertText(const std::string &text); + void insertText(LLWString &text); void appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo); // Non-undoable @@ -315,6 +320,7 @@ private: BOOL mAllowEmbeddedItems; bool mShowContextMenu; + bool mParseOnTheFly; LLUUID mSourceID; diff --git a/indra/llui/lltextparser.cpp b/indra/llui/lltextparser.cpp index a4fe4f6ca8..8a85f99e0c 100644 --- a/indra/llui/lltextparser.cpp +++ b/indra/llui/lltextparser.cpp @@ -46,8 +46,6 @@ LLTextParser::LLTextParser() {} -// Moved triggerAlerts() to llfloaterchat.cpp to break llui/llaudio library dependency. - S32 LLTextParser::findPattern(const std::string &text, LLSD highlight) { if (!highlight.has("pattern")) return -1; diff --git a/indra/llui/lltextvalidate.cpp b/indra/llui/lltextvalidate.cpp index 4b9faa0560..234e600ccd 100644 --- a/indra/llui/lltextvalidate.cpp +++ b/indra/llui/lltextvalidate.cpp @@ -188,6 +188,39 @@ namespace LLTextValidate return success; } + bool validateNonNegativeS32NoSpace(const LLWString &str) + { + LLLocale locale(LLLocale::USER_LOCALE); + + LLWString test_str = str; + S32 len = test_str.length(); + bool success = TRUE; + if(0 < len) + { + if('-' == test_str[0]) + { + success = FALSE; + } + S32 i = 0; + while(success && (i < len)) + { + if(!LLStringOps::isDigit(test_str[i]) || LLStringOps::isSpace(test_str[i++])) + { + success = FALSE; + } + } + } + if (success) + { + S32 val = strtol(wstring_to_utf8str(test_str).c_str(), NULL, 10); + if (val < 0) + { + success = FALSE; + } + } + return success; + } + bool validateAlphaNum(const LLWString &str) { LLLocale locale(LLLocale::USER_LOCALE); diff --git a/indra/llui/lltextvalidate.h b/indra/llui/lltextvalidate.h index 84644be30c..5c830d7db3 100644 --- a/indra/llui/lltextvalidate.h +++ b/indra/llui/lltextvalidate.h @@ -46,6 +46,7 @@ namespace LLTextValidate bool validateInt(const LLWString &str ); bool validatePositiveS32(const LLWString &str); bool validateNonNegativeS32(const LLWString &str); + bool validateNonNegativeS32NoSpace(const LLWString &str); bool validateAlphaNum(const LLWString &str ); bool validateAlphaNumSpace(const LLWString &str ); bool validateASCIIPrintableNoPipe(const LLWString &str); diff --git a/indra/llui/lltimectrl.cpp b/indra/llui/lltimectrl.cpp new file mode 100644 index 0000000000..9ea1e8815e --- /dev/null +++ b/indra/llui/lltimectrl.cpp @@ -0,0 +1,432 @@ +/** + * @file lltimectrl.cpp + * @brief LLTimeCtrl base class + * + * $LicenseInfo:firstyear=2001&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$ + */ + +#include "linden_common.h" + +#include "lltimectrl.h" + +#include "llui.h" +#include "lluiconstants.h" + +#include "llbutton.h" +#include "llfontgl.h" +#include "lllineeditor.h" +#include "llkeyboard.h" +#include "llstring.h" +#include "lltextbox.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register<LLTimeCtrl> time_r("time"); + +const U32 AMPM_LEN = 3; +const U32 MINUTES_MIN = 0; +const U32 MINUTES_MAX = 59; +const U32 HOURS_MIN = 1; +const U32 HOURS_MAX = 12; +const U32 MINUTES_PER_HOUR = 60; +const U32 MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR; + + +LLTimeCtrl::Params::Params() +: label_width("label_width"), + snap_to("snap_to"), + allow_text_entry("allow_text_entry", true), + text_enabled_color("text_enabled_color"), + text_disabled_color("text_disabled_color"), + up_button("up_button"), + down_button("down_button") +{} + +LLTimeCtrl::LLTimeCtrl(const LLTimeCtrl::Params& p) +: LLUICtrl(p), + mLabelBox(NULL), + mTextEnabledColor(p.text_enabled_color()), + mTextDisabledColor(p.text_disabled_color()), + mTime(0), + mSnapToMin(5) +{ + static LLUICachedControl<S32> spinctrl_spacing ("UISpinctrlSpacing", 0); + static LLUICachedControl<S32> spinctrl_btn_width ("UISpinctrlBtnWidth", 0); + static LLUICachedControl<S32> spinctrl_btn_height ("UISpinctrlBtnHeight", 0); + S32 centered_top = getRect().getHeight(); + S32 centered_bottom = getRect().getHeight() - 2 * spinctrl_btn_height; + S32 label_width = llclamp(p.label_width(), 0, llmax(0, getRect().getWidth() - 40)); + S32 editor_left = label_width + spinctrl_spacing; + + //================= Label =================// + if( !p.label().empty() ) + { + LLRect label_rect( 0, centered_top, label_width, centered_bottom ); + LLTextBox::Params params; + params.name("TimeCtrl Label"); + params.rect(label_rect); + params.initial_value(p.label()); + if (p.font.isProvided()) + { + params.font(p.font); + } + mLabelBox = LLUICtrlFactory::create<LLTextBox> (params); + addChild(mLabelBox); + + editor_left = label_rect.mRight + spinctrl_spacing; + } + + S32 editor_right = getRect().getWidth() - spinctrl_btn_width - spinctrl_spacing; + + //================= Editor ================// + LLRect editor_rect( editor_left, centered_top, editor_right, centered_bottom ); + LLLineEditor::Params params; + params.name("SpinCtrl Editor"); + params.rect(editor_rect); + if (p.font.isProvided()) + { + params.font(p.font); + } + + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + params.max_length.chars(8); + params.keystroke_callback(boost::bind(&LLTimeCtrl::onTextEntry, this, _1)); + mEditor = LLUICtrlFactory::create<LLLineEditor> (params); + mEditor->setPrevalidateInput(LLTextValidate::validateNonNegativeS32NoSpace); + mEditor->setPrevalidate(boost::bind(&LLTimeCtrl::isTimeStringValid, this, _1)); + mEditor->setText(LLStringExplicit("12:00 AM")); + addChild(mEditor); + + //================= Spin Buttons ==========// + LLButton::Params up_button_params(p.up_button); + up_button_params.rect = LLRect(editor_right + 1, getRect().getHeight(), editor_right + spinctrl_btn_width, getRect().getHeight() - spinctrl_btn_height); + + up_button_params.click_callback.function(boost::bind(&LLTimeCtrl::onUpBtn, this)); + up_button_params.mouse_held_callback.function(boost::bind(&LLTimeCtrl::onUpBtn, this)); + mUpBtn = LLUICtrlFactory::create<LLButton>(up_button_params); + addChild(mUpBtn); + + LLButton::Params down_button_params(p.down_button); + down_button_params.rect = LLRect(editor_right + 1, getRect().getHeight() - spinctrl_btn_height, editor_right + spinctrl_btn_width, getRect().getHeight() - 2 * spinctrl_btn_height); + down_button_params.click_callback.function(boost::bind(&LLTimeCtrl::onDownBtn, this)); + down_button_params.mouse_held_callback.function(boost::bind(&LLTimeCtrl::onDownBtn, this)); + mDownBtn = LLUICtrlFactory::create<LLButton>(down_button_params); + addChild(mDownBtn); + + setUseBoundingRect( TRUE ); +} + +F32 LLTimeCtrl::getTime24() const +{ + // 0.0 - 23.99; + return mTime / 60.0f; +} + +U32 LLTimeCtrl::getHours24() const +{ + return (U32) getTime24(); +} + +U32 LLTimeCtrl::getMinutes() const +{ + return mTime % MINUTES_PER_HOUR; +} + +void LLTimeCtrl::setTime24(F32 time) +{ + time = llclamp(time, 0.0f, 23.99f); // fix out of range values + mTime = llround(time * MINUTES_PER_HOUR); // fixes values like 4.99999 + + updateText(); +} + +BOOL LLTimeCtrl::handleKeyHere(KEY key, MASK mask) +{ + if (mEditor->hasFocus()) + { + if(key == KEY_UP) + { + onUpBtn(); + return TRUE; + } + if(key == KEY_DOWN) + { + onDownBtn(); + return TRUE; + } + if (key == KEY_RETURN) + { + onCommit(); + return TRUE; + } + } + return FALSE; +} + +void LLTimeCtrl::onUpBtn() +{ + switch(getEditingPart()) + { + case HOURS: + increaseHours(); + break; + case MINUTES: + increaseMinutes(); + break; + case DAYPART: + switchDayPeriod(); + break; + default: + break; + } + + updateText(); + onCommit(); +} + +void LLTimeCtrl::onDownBtn() +{ + switch(getEditingPart()) + { + case HOURS: + decreaseHours(); + break; + case MINUTES: + decreaseMinutes(); + break; + case DAYPART: + switchDayPeriod(); + break; + default: + break; + } + + updateText(); + onCommit(); +} + +void LLTimeCtrl::onFocusLost() +{ + updateText(); + onCommit(); + LLUICtrl::onFocusLost(); +} + +void LLTimeCtrl::onTextEntry(LLLineEditor* line_editor) +{ + std::string time_str = line_editor->getText(); + U32 h12 = parseHours(getHoursString(time_str)); + U32 m = parseMinutes(getMinutesString(time_str)); + bool pm = parseAMPM(getAMPMString(time_str)); + + if (h12 == 12) + { + h12 = 0; + } + + U32 h24 = pm ? h12 + 12 : h12; + + mTime = h24 * MINUTES_PER_HOUR + m; +} + +bool LLTimeCtrl::isTimeStringValid(const LLWString &wstr) +{ + std::string str = wstring_to_utf8str(wstr); + + return isHoursStringValid(getHoursString(str)) && + isMinutesStringValid(getMinutesString(str)) && + isPMAMStringValid(getAMPMString(str)); +} + +void LLTimeCtrl::increaseMinutes() +{ + mTime = (mTime + mSnapToMin) % MINUTES_PER_DAY - (mTime % mSnapToMin); +} + +void LLTimeCtrl::increaseHours() +{ + mTime = (mTime + MINUTES_PER_HOUR) % MINUTES_PER_DAY; +} + +void LLTimeCtrl::decreaseMinutes() +{ + if (mTime < mSnapToMin) + { + mTime = MINUTES_PER_DAY - mTime; + } + + mTime -= (mTime % mSnapToMin) ? mTime % mSnapToMin : mSnapToMin; +} + +void LLTimeCtrl::decreaseHours() +{ + if (mTime < MINUTES_PER_HOUR) + { + mTime = 23 * MINUTES_PER_HOUR + mTime; + } + else + { + mTime -= MINUTES_PER_HOUR; + } +} + +bool LLTimeCtrl::isPM() const +{ + return mTime >= (MINUTES_PER_DAY / 2); +} + +void LLTimeCtrl::switchDayPeriod() +{ + if (isPM()) + { + mTime -= MINUTES_PER_DAY / 2; + } + else + { + mTime += MINUTES_PER_DAY / 2; + } +} + +void LLTimeCtrl::updateText() +{ + U32 h24 = getHours24(); + U32 m = getMinutes(); + U32 h12 = h24 > 12 ? h24 - 12 : h24; + + if (h12 == 0) + h12 = 12; + + mEditor->setText(llformat("%d:%02d %s", h12, m, isPM() ? "PM":"AM")); +} + +LLTimeCtrl::EEditingPart LLTimeCtrl::getEditingPart() +{ + S32 cur_pos = mEditor->getCursor(); + std::string time_str = mEditor->getText(); + + S32 colon_index = time_str.find_first_of(':'); + + if (cur_pos <= colon_index) + { + return HOURS; + } + else if (cur_pos > colon_index && cur_pos <= (S32)(time_str.length() - AMPM_LEN)) + { + return MINUTES; + } + else if (cur_pos > (S32)(time_str.length() - AMPM_LEN)) + { + return DAYPART; + } + + return NONE; +} + +// static +std::string LLTimeCtrl::getHoursString(const std::string& str) +{ + size_t colon_index = str.find_first_of(':'); + std::string hours_str = str.substr(0, colon_index); + + return hours_str; +} + +// static +std::string LLTimeCtrl::getMinutesString(const std::string& str) +{ + size_t colon_index = str.find_first_of(':'); + ++colon_index; + + int minutes_len = str.length() - colon_index - AMPM_LEN; + std::string minutes_str = str.substr(colon_index, minutes_len); + + return minutes_str; +} + +// static +std::string LLTimeCtrl::getAMPMString(const std::string& str) +{ + return str.substr(str.size() - 2, 2); // returns last two characters +} + +// static +bool LLTimeCtrl::isHoursStringValid(const std::string& str) +{ + U32 hours; + if ((!LLStringUtil::convertToU32(str, hours) || (hours <= HOURS_MAX)) && str.length() < 3) + return true; + + return false; +} + +// static +bool LLTimeCtrl::isMinutesStringValid(const std::string& str) +{ + U32 minutes; + if (!LLStringUtil::convertToU32(str, minutes) || (minutes <= MINUTES_MAX) && str.length() < 3) + return true; + + return false; +} + +// static +bool LLTimeCtrl::isPMAMStringValid(const std::string& str) +{ + S32 len = str.length(); + + bool valid = (str[--len] == 'M') && (str[--len] == 'P' || str[len] == 'A'); + + return valid; +} + +// static +U32 LLTimeCtrl::parseHours(const std::string& str) +{ + U32 hours; + if (LLStringUtil::convertToU32(str, hours) && (hours >= HOURS_MIN) && (hours <= HOURS_MAX)) + { + return hours; + } + else + { + return HOURS_MIN; + } +} + +// static +U32 LLTimeCtrl::parseMinutes(const std::string& str) +{ + U32 minutes; + if (LLStringUtil::convertToU32(str, minutes) && (minutes >= MINUTES_MIN) && (minutes <= MINUTES_MAX)) + { + return minutes; + } + else + { + return MINUTES_MIN; + } +} + +// static +bool LLTimeCtrl::parseAMPM(const std::string& str) +{ + return str == "PM"; +} diff --git a/indra/llui/lltimectrl.h b/indra/llui/lltimectrl.h new file mode 100644 index 0000000000..b5f268c76a --- /dev/null +++ b/indra/llui/lltimectrl.h @@ -0,0 +1,131 @@ +/** + * @file lltimectrl.h + * @brief Time control + * + * $LicenseInfo:firstyear=2002&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 LLTIMECTRL_H_ +#define LLTIMECTRL_H_ + +#include "stdtypes.h" +#include "llbutton.h" +#include "v4color.h" +#include "llrect.h" + +class LLLineEditor; + +class LLTimeCtrl +: public LLUICtrl +{ + LOG_CLASS(LLTimeCtrl); +public: + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<S32> label_width; + Optional<S32> snap_to; + Optional<bool> allow_text_entry; + + Optional<LLUIColor> text_enabled_color; + Optional<LLUIColor> text_disabled_color; + + Optional<LLButton::Params> up_button; + Optional<LLButton::Params> down_button; + + Params(); + }; + + F32 getTime24() const; // 0.0 - 24.0 + U32 getHours24() const; // 0 - 23 + U32 getMinutes() const; // 0 - 59 + + void setTime24(F32 time); // 0.0 - 23.98(3) + +protected: + LLTimeCtrl(const Params&); + friend class LLUICtrlFactory; + +private: + + enum EDayPeriod + { + AM, + PM + }; + + enum EEditingPart + { + HOURS, + MINUTES, + DAYPART, + NONE + }; + + virtual void onFocusLost(); + virtual BOOL handleKeyHere(KEY key, MASK mask); + + void onUpBtn(); + void onDownBtn(); + void onTextEntry(LLLineEditor* line_editor); + + bool isTimeStringValid(const LLWString& wstr); + + void increaseMinutes(); + void increaseHours(); + + void decreaseMinutes(); + void decreaseHours(); + + bool isPM() const; + void switchDayPeriod(); + + void updateText(); + + EEditingPart getEditingPart(); + + static std::string getHoursString(const std::string& str); + static std::string getMinutesString(const std::string& str); + static std::string getAMPMString(const std::string& str); + + static bool isHoursStringValid(const std::string& str); + static bool isMinutesStringValid(const std::string& str); + static bool isPMAMStringValid(const std::string& str); + + static U32 parseHours(const std::string& str); + static U32 parseMinutes(const std::string& str); + static bool parseAMPM(const std::string& str); + + class LLTextBox* mLabelBox; + + class LLLineEditor* mEditor; + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; + + class LLButton* mUpBtn; + class LLButton* mDownBtn; + + U32 mTime; // minutes since midnight: 0 - 1439 + U32 mSnapToMin; // interval in minutes to snap to + + BOOL mAllowEdit; +}; +#endif /* LLTIMECTRL_H_ */ diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp new file mode 100644 index 0000000000..81ea0ebf0c --- /dev/null +++ b/indra/llui/lltoolbar.cpp @@ -0,0 +1,1230 @@ +/** + * @file lltoolbar.cpp + * @author Richard Nelson + * @brief User customizable toolbar class + * + * $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$ + */ + +#include "linden_common.h" + +#include <boost/foreach.hpp> +#include "lltoolbar.h" + +#include "llcommandmanager.h" +#include "llmenugl.h" +#include "lltrans.h" +#include "llinventory.h" +#include "lliconctrl.h" + +// uncomment this and remove the one in llui.cpp when there is an external reference to this translation unit +// thanks, MSVC! +//static LLDefaultChildRegistry::Register<LLToolBar> r1("toolbar"); + +namespace LLToolBarEnums +{ + LLLayoutStack::ELayoutOrientation getOrientation(SideType sideType) + { + LLLayoutStack::ELayoutOrientation orientation = LLLayoutStack::HORIZONTAL; + + if ((sideType == SIDE_LEFT) || (sideType == SIDE_RIGHT)) + { + orientation = LLLayoutStack::VERTICAL; + } + + return orientation; + } +} + +using namespace LLToolBarEnums; + + +namespace LLInitParam +{ + void TypeValues<ButtonType>::declareValues() + { + declare("icons_with_text", BTNTYPE_ICONS_WITH_TEXT); + declare("icons_only", BTNTYPE_ICONS_ONLY); + } + + void TypeValues<SideType>::declareValues() + { + declare("bottom", SIDE_BOTTOM); + declare("left", SIDE_LEFT); + declare("right", SIDE_RIGHT); + declare("top", SIDE_TOP); + } +} + +LLToolBar::Params::Params() +: button_display_mode("button_display_mode"), + commands("command"), + side("side", SIDE_TOP), + button_icon("button_icon"), + button_icon_and_text("button_icon_and_text"), + read_only("read_only", false), + wrap("wrap", true), + pad_left("pad_left"), + pad_top("pad_top"), + pad_right("pad_right"), + pad_bottom("pad_bottom"), + pad_between("pad_between"), + min_girth("min_girth"), + button_panel("button_panel") +{} + +LLToolBar::LLToolBar(const LLToolBar::Params& p) +: LLUICtrl(p), + mReadOnly(p.read_only), + mButtonType(p.button_display_mode), + mSideType(p.side), + mWrap(p.wrap), + mNeedsLayout(false), + mModified(false), + mButtonPanel(NULL), + mCenteringStack(NULL), + mPadLeft(p.pad_left), + mPadRight(p.pad_right), + mPadTop(p.pad_top), + mPadBottom(p.pad_bottom), + mPadBetween(p.pad_between), + mMinGirth(p.min_girth), + mPopupMenuHandle(), + mRightMouseTargetButton(NULL), + mStartDragItemCallback(NULL), + mHandleDragItemCallback(NULL), + mHandleDropCallback(NULL), + mButtonAddSignal(NULL), + mButtonEnterSignal(NULL), + mButtonLeaveSignal(NULL), + mButtonRemoveSignal(NULL), + mDragAndDropTarget(false) +{ + mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_WITH_TEXT] = p.button_icon_and_text; + mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_ONLY] = p.button_icon; +} + +LLToolBar::~LLToolBar() +{ + delete mPopupMenuHandle.get(); + delete mButtonAddSignal; + delete mButtonEnterSignal; + delete mButtonLeaveSignal; + delete mButtonRemoveSignal; +} + +void LLToolBar::createContextMenu() +{ + if (!mPopupMenuHandle.get()) + { + // Setup bindings specific to this instance for the context menu options + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commit_reg; + commit_reg.add("Toolbars.EnableSetting", boost::bind(&LLToolBar::onSettingEnable, this, _2)); + commit_reg.add("Toolbars.RemoveSelectedCommand", boost::bind(&LLToolBar::onRemoveSelectedCommand, this)); + + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_reg; + enable_reg.add("Toolbars.CheckSetting", boost::bind(&LLToolBar::isSettingChecked, this, _2)); + + // Create the context menu + LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>("menu_toolbars.xml", LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + + if (menu) + { + menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); + mPopupMenuHandle = menu->getHandle(); + mRemoveButtonHandle = menu->getChild<LLView>("Remove button")->getHandle(); + } + else + { + llwarns << "Unable to load toolbars context menu." << llendl; + } + } + + if (mRemoveButtonHandle.get()) + { + // Disable/Enable the "Remove button" menu item depending on whether or not a button was clicked + mRemoveButtonHandle.get()->setEnabled(mRightMouseTargetButton != NULL); + } +} + +void LLToolBar::initFromParams(const LLToolBar::Params& p) +{ + // Initialize the base object + LLUICtrl::initFromParams(p); + + LLLayoutStack::ELayoutOrientation orientation = getOrientation(p.side); + + LLLayoutStack::Params centering_stack_p; + centering_stack_p.name = "centering_stack"; + centering_stack_p.rect = getLocalRect(); + centering_stack_p.follows.flags = FOLLOWS_ALL; + centering_stack_p.orientation = orientation; + centering_stack_p.mouse_opaque = false; + + mCenteringStack = LLUICtrlFactory::create<LLLayoutStack>(centering_stack_p); + addChild(mCenteringStack); + + LLLayoutPanel::Params border_panel_p; + border_panel_p.name = "border_panel"; + border_panel_p.rect = getLocalRect(); + border_panel_p.auto_resize = true; + border_panel_p.user_resize = false; + border_panel_p.mouse_opaque = false; + + mCenteringStack->addChild(LLUICtrlFactory::create<LLLayoutPanel>(border_panel_p)); + + LLLayoutPanel::Params center_panel_p; + center_panel_p.name = "center_panel"; + center_panel_p.rect = getLocalRect(); + center_panel_p.auto_resize = false; + center_panel_p.user_resize = false; + center_panel_p.mouse_opaque = false; + LLLayoutPanel* center_panel = LLUICtrlFactory::create<LLLayoutPanel>(center_panel_p); + mCenteringStack->addChild(center_panel); + + LLPanel::Params button_panel_p(p.button_panel); + button_panel_p.rect = center_panel->getLocalRect(); + button_panel_p.follows.flags = FOLLOWS_BOTTOM|FOLLOWS_LEFT; + mButtonPanel = LLUICtrlFactory::create<LLPanel>(button_panel_p); + center_panel->addChild(mButtonPanel); + + mCenteringStack->addChild(LLUICtrlFactory::create<LLLayoutPanel>(border_panel_p)); + + BOOST_FOREACH(LLCommandId id, p.commands) + { + addCommand(id); + } + + mNeedsLayout = true; +} + +bool LLToolBar::addCommand(const LLCommandId& commandId, int rank) +{ + LLCommand * command = LLCommandManager::instance().getCommand(commandId); + if (!command) return false; + + // Create the button and do the things that don't need ordering + LLToolBarButton* button = createButton(commandId); + mButtonPanel->addChild(button); + mButtonMap.insert(std::make_pair(commandId.uuid(), button)); + + // Insert the command and button in the right place in their respective lists + if ((rank >= mButtonCommands.size()) || (rank == RANK_NONE)) + { + // In that case, back load + mButtonCommands.push_back(command->id()); + mButtons.push_back(button); + } + else + { + // Insert in place: iterate to the right spot... + std::list<LLToolBarButton*>::iterator it_button = mButtons.begin(); + command_id_list_t::iterator it_command = mButtonCommands.begin(); + while (rank > 0) + { + ++it_button; + ++it_command; + rank--; + } + // ...then insert + mButtonCommands.insert(it_command, command->id()); + mButtons.insert(it_button,button); + } + + mNeedsLayout = true; + + updateLayoutAsNeeded(); + + + if (mButtonAddSignal) + { + (*mButtonAddSignal)(button); + } + + return true; +} + +// Remove a command from the list +// Returns the rank of the command in the original list so that doing addCommand(id,rank) right after +// a removeCommand(id) would leave the list unchanged. +// Returns RANK_NONE if the command is not found in the list +int LLToolBar::removeCommand(const LLCommandId& commandId) +{ + if (!hasCommand(commandId)) return RANK_NONE; + + // First erase the map record + command_id_map::iterator it = mButtonMap.find(commandId.uuid()); + mButtonMap.erase(it); + + // Now iterate on the commands and buttons to identify the relevant records + int rank = 0; + std::list<LLToolBarButton*>::iterator it_button = mButtons.begin(); + command_id_list_t::iterator it_command = mButtonCommands.begin(); + while (*it_command != commandId) + { + ++it_button; + ++it_command; + ++rank; + } + + if (mButtonRemoveSignal) + { + (*mButtonRemoveSignal)(*it_button); + } + + // Delete the button and erase the command and button records + delete (*it_button); + mButtonCommands.erase(it_command); + mButtons.erase(it_button); + + mNeedsLayout = true; + + return rank; +} + +void LLToolBar::clearCommandsList() +{ + // Clears the commands list + mButtonCommands.clear(); + // This will clear the buttons + createButtons(); +} + +bool LLToolBar::hasCommand(const LLCommandId& commandId) const +{ + if (commandId != LLCommandId::null) + { + command_id_map::const_iterator it = mButtonMap.find(commandId.uuid()); + return (it != mButtonMap.end()); + } + + return false; +} + +bool LLToolBar::enableCommand(const LLCommandId& commandId, bool enabled) +{ + LLButton * command_button = NULL; + + if (commandId != LLCommandId::null) + { + command_id_map::iterator it = mButtonMap.find(commandId.uuid()); + if (it != mButtonMap.end()) + { + command_button = it->second; + command_button->setEnabled(enabled); + } + } + + return (command_button != NULL); +} + +bool LLToolBar::stopCommandInProgress(const LLCommandId& commandId) +{ + // + // Note from Leslie: + // + // This implementation was largely put in place to handle EXP-1348 which is related to + // dragging and dropping the "speak" button. The "speak" button can be in one of two + // modes, i.e., either a toggle action or a push-to-talk action. Because of this it + // responds to mouse down and mouse up in different ways, based on which behavior the + // button is currently set to obey. This was the simplest way of getting the button + // to turn off the microphone for both behaviors without risking duplicate state. + // + + LLToolBarButton * command_button = NULL; + + if (commandId != LLCommandId::null) + { + LLCommand* command = LLCommandManager::instance().getCommand(commandId); + llassert(command); + + // If this command has an explicit function for execution stop + if (command->executeStopFunctionName().length() > 0) + { + command_id_map::iterator it = mButtonMap.find(commandId.uuid()); + if (it != mButtonMap.end()) + { + command_button = it->second; + llassert(command_button->mIsRunningSignal); + + // Check to see if it is running + if ((*command_button->mIsRunningSignal)(command_button, command->isRunningParameters())) + { + // Trigger an additional button commit, which calls mouse down, mouse up and commit + command_button->onCommit(); + } + } + } + } + + return (command_button != NULL); +} + +bool LLToolBar::flashCommand(const LLCommandId& commandId, bool flash) +{ + LLButton * command_button = NULL; + + if (commandId != LLCommandId::null) + { + command_id_map::iterator it = mButtonMap.find(commandId.uuid()); + if (it != mButtonMap.end()) + { + command_button = it->second; + command_button->setFlashing(flash ? TRUE : FALSE); + } + } + + return (command_button != NULL); +} + +BOOL LLToolBar::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLRect button_panel_rect; + mButtonPanel->localRectToOtherView(mButtonPanel->getLocalRect(), &button_panel_rect, this); + BOOL handle_it_here = !mReadOnly && button_panel_rect.pointInRect(x, y); + + if (handle_it_here) + { + // Determine which button the mouse was over during the click in case the context menu action + // is intended to affect the button. + mRightMouseTargetButton = NULL; + BOOST_FOREACH(LLToolBarButton* button, mButtons) + { + LLRect button_rect; + button->localRectToOtherView(button->getLocalRect(), &button_rect, this); + + if (button_rect.pointInRect(x, y)) + { + mRightMouseTargetButton = button; + break; + } + } + + createContextMenu(); + + LLContextMenu * menu = (LLContextMenu *) mPopupMenuHandle.get(); + + if (menu) + { + menu->show(x, y); + + LLMenuGL::showPopup(this, menu, x, y); + } + } + + return handle_it_here; +} + +BOOL LLToolBar::isSettingChecked(const LLSD& userdata) +{ + BOOL retval = FALSE; + + const std::string setting_name = userdata.asString(); + + if (setting_name == "icons_with_text") + { + retval = (mButtonType == BTNTYPE_ICONS_WITH_TEXT); + } + else if (setting_name == "icons_only") + { + retval = (mButtonType == BTNTYPE_ICONS_ONLY); + } + + return retval; +} + +void LLToolBar::onSettingEnable(const LLSD& userdata) +{ + llassert(!mReadOnly); + + const std::string setting_name = userdata.asString(); + + if (setting_name == "icons_with_text") + { + setButtonType(BTNTYPE_ICONS_WITH_TEXT); + } + else if (setting_name == "icons_only") + { + setButtonType(BTNTYPE_ICONS_ONLY); + } +} + +void LLToolBar::onRemoveSelectedCommand() +{ + llassert(!mReadOnly); + + if (mRightMouseTargetButton) + { + removeCommand(mRightMouseTargetButton->getCommandId()); + + mRightMouseTargetButton = NULL; + } +} + +void LLToolBar::setButtonType(LLToolBarEnums::ButtonType button_type) +{ + bool regenerate_buttons = (mButtonType != button_type); + + mButtonType = button_type; + + if (regenerate_buttons) + { + createButtons(); + } +} + +void LLToolBar::resizeButtonsInRow(std::vector<LLToolBarButton*>& buttons_in_row, S32 max_row_girth) +{ + // make buttons in current row all same girth + BOOST_FOREACH(LLToolBarButton* button, buttons_in_row) + { + if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) + { + button->reshape(button->mWidthRange.clamp(button->getRect().getWidth()), max_row_girth); + } + else // VERTICAL + { + button->reshape(max_row_girth, button->getRect().getHeight()); + } + } +} + +// Returns the position of the coordinates as a rank in the button list. +// The rank is the position a tool dropped in (x,y) would assume in the button list. +// The returned value is between 0 and mButtons.size(), 0 being the first element to the left +// (or top) and mButtons.size() the last one to the right (or bottom). +// Various drag data are stored in the toolbar object though are not exposed outside (and shouldn't). +int LLToolBar::getRankFromPosition(S32 x, S32 y) +{ + if (mButtons.empty()) + { + return RANK_NONE; + } + + int rank = 0; + + // Convert the toolbar coord into button panel coords + LLLayoutStack::ELayoutOrientation orientation = getOrientation(mSideType); + S32 button_panel_x = 0; + S32 button_panel_y = 0; + localPointToOtherView(x, y, &button_panel_x, &button_panel_y, mButtonPanel); + S32 dx = x - button_panel_x; + S32 dy = y - button_panel_y; + + // Simply compare the passed coord with the buttons outbound box + padding + std::list<LLToolBarButton*>::iterator it_button = mButtons.begin(); + std::list<LLToolBarButton*>::iterator end_button = mButtons.end(); + LLRect button_rect; + while (it_button != end_button) + { + button_rect = (*it_button)->getRect(); + S32 point_x = button_rect.mRight + mPadRight; + S32 point_y = button_rect.mBottom - mPadBottom; + + if ((button_panel_x < point_x) && (button_panel_y > point_y)) + { + break; + } + rank++; + ++it_button; + } + + // Update the passed coordinates to the hit button relevant corner + // (different depending on toolbar orientation) + if (rank < mButtons.size()) + { + if (orientation == LLLayoutStack::HORIZONTAL) + { + // Horizontal + S32 mid_point = (button_rect.mRight + button_rect.mLeft) / 2; + if (button_panel_x < mid_point) + { + mDragx = button_rect.mLeft - mPadLeft; + mDragy = button_rect.mTop + mPadTop; + } + else + { + rank++; + mDragx = button_rect.mRight + mPadRight - 1; + mDragy = button_rect.mTop + mPadTop; + } + } + else + { + // Vertical + S32 mid_point = (button_rect.mTop + button_rect.mBottom) / 2; + if (button_panel_y > mid_point) + { + mDragx = button_rect.mLeft - mPadLeft; + mDragy = button_rect.mTop + mPadTop; + } + else + { + rank++; + mDragx = button_rect.mLeft - mPadLeft; + mDragy = button_rect.mBottom - mPadBottom + 1; + } + } + } + else + { + // We hit passed the end of the list so put the insertion point at the end + if (orientation == LLLayoutStack::HORIZONTAL) + { + mDragx = button_rect.mRight + mPadRight; + mDragy = button_rect.mTop + mPadTop; + } + else + { + mDragx = button_rect.mLeft - mPadLeft; + mDragy = button_rect.mBottom - mPadBottom; + } + } + + // Update the "girth" of the caret, i.e. the width or height (depending of orientation) + if (orientation == LLLayoutStack::HORIZONTAL) + { + mDragGirth = button_rect.getHeight() + mPadBottom + mPadTop; + } + else + { + mDragGirth = button_rect.getWidth() + mPadLeft + mPadRight; + } + + // The delta account for the coord model change (i.e. convert back to toolbar coord) + mDragx += dx; + mDragy += dy; + + return rank; +} + +int LLToolBar::getRankFromPosition(const LLCommandId& id) +{ + if (!hasCommand(id)) + { + return RANK_NONE; + } + int rank = 0; + std::list<LLToolBarButton*>::iterator it_button = mButtons.begin(); + std::list<LLToolBarButton*>::iterator end_button = mButtons.end(); + while (it_button != end_button) + { + if ((*it_button)->mId == id) + { + break; + } + rank++; + ++it_button; + } + return rank; +} + +void LLToolBar::updateLayoutAsNeeded() +{ + if (!mNeedsLayout) return; + + LLLayoutStack::ELayoutOrientation orientation = getOrientation(mSideType); + + // our terminology for orientation-agnostic layout is such that + // length refers to a distance in the direction we stack the buttons + // and girth refers to a distance in the direction buttons wrap + S32 max_row_girth = 0; + S32 max_row_length = 0; + + S32 max_length; + S32 max_total_girth; + S32 cur_start; + S32 cur_row ; + S32 row_pad_start; + S32 row_pad_end; + S32 girth_pad_end; + S32 row_running_length; + + if (orientation == LLLayoutStack::HORIZONTAL) + { + max_length = getRect().getWidth() - mPadLeft - mPadRight; + max_total_girth = getRect().getHeight() - mPadTop - mPadBottom; + row_pad_start = mPadLeft; + row_pad_end = mPadRight; + cur_row = mPadTop; + girth_pad_end = mPadBottom; + } + else // VERTICAL + { + max_length = getRect().getHeight() - mPadTop - mPadBottom; + max_total_girth = getRect().getWidth() - mPadLeft - mPadRight; + row_pad_start = mPadTop; + row_pad_end = mPadBottom; + cur_row = mPadLeft; + girth_pad_end = mPadRight; + } + + row_running_length = row_pad_start; + cur_start = row_pad_start; + + + LLRect panel_rect = mButtonPanel->getLocalRect(); + + std::vector<LLToolBarButton*> buttons_in_row; + + BOOST_FOREACH(LLToolBarButton* button, mButtons) + { + button->reshape(button->mWidthRange.getMin(), button->mDesiredHeight); + button->autoResize(); + + S32 button_clamped_width = button->mWidthRange.clamp(button->getRect().getWidth()); + S32 button_length = (orientation == LLLayoutStack::HORIZONTAL) + ? button_clamped_width + : button->getRect().getHeight(); + S32 button_girth = (orientation == LLLayoutStack::HORIZONTAL) + ? button->getRect().getHeight() + : button_clamped_width; + + // wrap if needed + if (mWrap + && row_running_length + button_length > max_length // out of room... + && cur_start != row_pad_start) // ...and not first button in row + { + if (orientation == LLLayoutStack::VERTICAL) + { // row girth (width in this case) is clamped to allowable button widths + max_row_girth = button->mWidthRange.clamp(max_row_girth); + } + + // make buttons in current row all same girth + resizeButtonsInRow(buttons_in_row, max_row_girth); + buttons_in_row.clear(); + + max_row_length = llmax(max_row_length, row_running_length); + row_running_length = row_pad_start; + cur_start = row_pad_start; + cur_row += max_row_girth + mPadBetween; + max_row_girth = 0; + } + + LLRect button_rect; + if (orientation == LLLayoutStack::HORIZONTAL) + { + button_rect.setLeftTopAndSize(cur_start, panel_rect.mTop - cur_row, button_clamped_width, button->getRect().getHeight()); + } + else // VERTICAL + { + button_rect.setLeftTopAndSize(cur_row, panel_rect.mTop - cur_start, button_clamped_width, button->getRect().getHeight()); + } + button->setShape(button_rect); + + buttons_in_row.push_back(button); + + row_running_length += button_length + mPadBetween; + cur_start = row_running_length; + max_row_girth = llmax(button_girth, max_row_girth); + } + + // final resizing in "girth" direction + S32 total_girth = cur_row // current row position... + + max_row_girth // ...incremented by size of final row... + + girth_pad_end; // ...plus padding reserved on end + total_girth = llmax(total_girth,mMinGirth); + + max_row_length = llmax(max_row_length, row_running_length - mPadBetween + row_pad_end); + + resizeButtonsInRow(buttons_in_row, max_row_girth); + + // grow and optionally shift toolbar to accommodate buttons + if (orientation == LLLayoutStack::HORIZONTAL) + { + if (mSideType == SIDE_TOP) + { // shift down to maintain top edge + translate(0, getRect().getHeight() - total_girth); + } + + reshape(getRect().getWidth(), total_girth); + mButtonPanel->reshape(max_row_length, total_girth); + } + else // VERTICAL + { + if (mSideType == SIDE_RIGHT) + { // shift left to maintain right edge + translate(getRect().getWidth() - total_girth, 0); + } + + reshape(total_girth, getRect().getHeight()); + mButtonPanel->reshape(total_girth, max_row_length); + } + + // make parent fit button panel + mButtonPanel->getParent()->setShape(mButtonPanel->getLocalRect()); + + // re-center toolbar buttons + mCenteringStack->updateLayout(); + + if (!mButtons.empty()) + { + mButtonPanel->setVisible(TRUE); + mButtonPanel->setMouseOpaque(TRUE); + } + + // don't clear flag until after we've resized ourselves, to avoid laying out every frame + mNeedsLayout = false; +} + + +void LLToolBar::draw() +{ + if (mButtons.empty()) + { + mButtonPanel->setVisible(FALSE); + mButtonPanel->setMouseOpaque(FALSE); + } + else + { + mButtonPanel->setVisible(TRUE); + mButtonPanel->setMouseOpaque(TRUE); + } + + // Update enable/disable state and highlight state for editable toolbars + if (!mReadOnly) + { + for (toolbar_button_list::iterator btn_it = mButtons.begin(); btn_it != mButtons.end(); ++btn_it) + { + LLToolBarButton* btn = *btn_it; + LLCommand* command = LLCommandManager::instance().getCommand(btn->mId); + + if (command && btn->mIsEnabledSignal) + { + const bool button_command_enabled = (*btn->mIsEnabledSignal)(btn, command->isEnabledParameters()); + btn->setEnabled(button_command_enabled); + } + + if (command && btn->mIsRunningSignal) + { + const bool button_command_running = (*btn->mIsRunningSignal)(btn, command->isRunningParameters()); + btn->setToggleState(button_command_running); + } + } + } + + updateLayoutAsNeeded(); + // rect may have shifted during layout + LLUI::popMatrix(); + LLUI::pushMatrix(); + LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); + + // Position the caret + LLIconCtrl* caret = getChild<LLIconCtrl>("caret"); + caret->setVisible(FALSE); + if (mDragAndDropTarget && !mButtonCommands.empty()) + { + LLRect caret_rect = caret->getRect(); + LLRect toolbar_rect = getRect(); + if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) + { + caret->setRect(LLRect(mDragx-caret_rect.getWidth()/2+1, + mDragy, + mDragx+caret_rect.getWidth()/2+1, + mDragy-mDragGirth)); + } + else + { + caret->setRect(LLRect(mDragx, + mDragy+caret_rect.getHeight()/2, + mDragx+mDragGirth, + mDragy-caret_rect.getHeight()/2)); + } + caret->setVisible(TRUE); + } + + LLUICtrl::draw(); + caret->setVisible(FALSE); + mDragAndDropTarget = false; +} + +void LLToolBar::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLUICtrl::reshape(width, height, called_from_parent); + mNeedsLayout = true; +} + +void LLToolBar::createButtons() +{ + BOOST_FOREACH(LLToolBarButton* button, mButtons) + { + if (mButtonRemoveSignal) + { + (*mButtonRemoveSignal)(button); + } + + delete button; + } + mButtons.clear(); + mButtonMap.clear(); + mRightMouseTargetButton = NULL; + + BOOST_FOREACH(LLCommandId& command_id, mButtonCommands) + { + LLToolBarButton* button = createButton(command_id); + mButtons.push_back(button); + mButtonPanel->addChild(button); + mButtonMap.insert(std::make_pair(command_id.uuid(), button)); + + if (mButtonAddSignal) + { + (*mButtonAddSignal)(button); + } + } + mNeedsLayout = true; +} + +void LLToolBarButton::callIfEnabled(LLUICtrl::commit_callback_t commit, LLUICtrl* ctrl, const LLSD& param ) +{ + LLCommand* command = LLCommandManager::instance().getCommand(mId); + + if (!mIsEnabledSignal || (*mIsEnabledSignal)(this, command->isEnabledParameters())) + { + commit(ctrl, param); + } +} + +LLToolBarButton* LLToolBar::createButton(const LLCommandId& id) +{ + LLCommand* commandp = LLCommandManager::instance().getCommand(id); + if (!commandp) return NULL; + + LLToolBarButton::Params button_p; + button_p.name = commandp->name(); + button_p.label = LLTrans::getString(commandp->labelRef()); + button_p.tool_tip = LLTrans::getString(commandp->tooltipRef()); + button_p.image_overlay = LLUI::getUIImage(commandp->icon()); + button_p.overwriteFrom(mButtonParams[mButtonType]); + LLToolBarButton* button = LLUICtrlFactory::create<LLToolBarButton>(button_p); + + if (!mReadOnly) + { + enable_callback_t isEnabledCB; + + const std::string& isEnabledFunction = commandp->isEnabledFunctionName(); + if (isEnabledFunction.length() > 0) + { + LLUICtrl::EnableCallbackParam isEnabledParam; + isEnabledParam.function_name = isEnabledFunction; + isEnabledParam.parameter = commandp->isEnabledParameters(); + isEnabledCB = initEnableCallback(isEnabledParam); + + if (NULL == button->mIsEnabledSignal) + { + button->mIsEnabledSignal = new enable_signal_t(); + } + + button->mIsEnabledSignal->connect(isEnabledCB); + } + + LLUICtrl::CommitCallbackParam executeParam; + executeParam.function_name = commandp->executeFunctionName(); + executeParam.parameter = commandp->executeParameters(); + + // If we have a "stop" function then we map the command to mouse down / mouse up otherwise commit + const std::string& executeStopFunction = commandp->executeStopFunctionName(); + if (executeStopFunction.length() > 0) + { + LLUICtrl::CommitCallbackParam executeStopParam; + executeStopParam.function_name = executeStopFunction; + executeStopParam.parameter = commandp->executeStopParameters(); + LLUICtrl::commit_callback_t execute_func = initCommitCallback(executeParam); + LLUICtrl::commit_callback_t stop_func = initCommitCallback(executeStopParam); + + button->setMouseDownCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, execute_func, _1, _2)); + button->setMouseUpCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, stop_func, _1, _2)); + } + else + { + button->setCommitCallback(executeParam); + } + + // Set up "is running" query callback + const std::string& isRunningFunction = commandp->isRunningFunctionName(); + if (isRunningFunction.length() > 0) + { + LLUICtrl::EnableCallbackParam isRunningParam; + isRunningParam.function_name = isRunningFunction; + isRunningParam.parameter = commandp->isRunningParameters(); + enable_signal_t::slot_type isRunningCB = initEnableCallback(isRunningParam); + + if (NULL == button->mIsRunningSignal) + { + button->mIsRunningSignal = new enable_signal_t(); + } + + button->mIsRunningSignal->connect(isRunningCB); + } + } + + // Drag and drop behavior must work also if provided in the Toybox and, potentially, any read-only toolbar + button->setStartDragCallback(mStartDragItemCallback); + button->setHandleDragCallback(mHandleDragItemCallback); + + button->setCommandId(id); + + return button; +} + +boost::signals2::connection connectSignal(LLToolBar::button_signal_t*& signal, const LLToolBar::button_signal_t::slot_type& cb) +{ + if (!signal) + { + signal = new LLToolBar::button_signal_t(); + } + + return signal->connect(cb); +} + +boost::signals2::connection LLToolBar::setButtonAddCallback(const button_signal_t::slot_type& cb) +{ + return connectSignal(mButtonAddSignal, cb); +} + +boost::signals2::connection LLToolBar::setButtonEnterCallback(const button_signal_t::slot_type& cb) +{ + return connectSignal(mButtonEnterSignal, cb); +} + +boost::signals2::connection LLToolBar::setButtonLeaveCallback(const button_signal_t::slot_type& cb) +{ + return connectSignal(mButtonLeaveSignal, cb); +} + +boost::signals2::connection LLToolBar::setButtonRemoveCallback(const button_signal_t::slot_type& cb) +{ + return connectSignal(mButtonRemoveSignal, cb); +} + +BOOL LLToolBar::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // If we have a drop callback, that means that we can handle the drop + BOOL handled = (mHandleDropCallback ? TRUE : FALSE); + + // if drop is set, it's time to call the callback to get the operation done + if (handled && drop) + { + handled = mHandleDropCallback(cargo_data, x, y ,this); + } + + // We accept only single tool drop on toolbars + *accept = (handled ? ACCEPT_YES_SINGLE : ACCEPT_NO); + + // We'll use that flag to change the visual aspect of the toolbar target on draw() + mDragAndDropTarget = false; + + // Convert drag position into insert position and rank + if (!isReadOnly() && handled && !drop) + { + LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; + LLAssetType::EType type = inv_item->getType(); + if (type == LLAssetType::AT_WIDGET) + { + LLCommandId dragged_command(inv_item->getUUID()); + int orig_rank = getRankFromPosition(dragged_command); + mDragRank = getRankFromPosition(x, y); + // Don't DaD if we're dragging a command on itself + mDragAndDropTarget = ((orig_rank != RANK_NONE) && ((mDragRank == orig_rank) || ((mDragRank-1) == orig_rank)) ? false : true); + //llinfos << "Merov debug : DaD, rank = " << mDragRank << ", dragged uui = " << inv_item->getUUID() << llendl; + /* Do the following if you want to animate the button itself + LLCommandId dragged_command(inv_item->getUUID()); + removeCommand(dragged_command); + addCommand(dragged_command,rank); + */ + } + else + { + handled = FALSE; + } + } + + return handled; +} + +LLToolBarButton::LLToolBarButton(const Params& p) +: LLButton(p), + mMouseDownX(0), + mMouseDownY(0), + mWidthRange(p.button_width), + mDesiredHeight(p.desired_height), + mId(""), + mIsEnabledSignal(NULL), + mIsRunningSignal(NULL), + mIsStartingSignal(NULL), + mIsDragged(false), + mStartDragItemCallback(NULL), + mHandleDragItemCallback(NULL), + mOriginalImageSelected(p.image_selected), + mOriginalImageUnselected(p.image_unselected), + mOriginalImagePressed(p.image_pressed), + mOriginalImagePressedSelected(p.image_pressed_selected), + mOriginalLabelColor(p.label_color), + mOriginalLabelColorSelected(p.label_color_selected), + mOriginalImageOverlayColor(p.image_overlay_color), + mOriginalImageOverlaySelectedColor(p.image_overlay_selected_color) +{ +} + +LLToolBarButton::~LLToolBarButton() +{ + delete mIsEnabledSignal; + delete mIsRunningSignal; + delete mIsStartingSignal; +} + +BOOL LLToolBarButton::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mMouseDownX = x; + mMouseDownY = y; + return LLButton::handleMouseDown(x, y, mask); +} + +BOOL LLToolBarButton::handleHover(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + + S32 mouse_distance_squared = (x - mMouseDownX) * (x - mMouseDownX) + (y - mMouseDownY) * (y - mMouseDownY); + S32 drag_threshold = LLUI::sSettingGroups["config"]->getS32("DragAndDropDistanceThreshold"); + if (mouse_distance_squared > drag_threshold * drag_threshold + && hasMouseCapture() && + mStartDragItemCallback && mHandleDragItemCallback) + { + if (!mIsDragged) + { + mStartDragItemCallback(x, y, this); + mIsDragged = true; + handled = TRUE; + } + else + { + handled = mHandleDragItemCallback(x, y, mId.uuid(), LLAssetType::AT_WIDGET); + } + } + else + { + handled = LLButton::handleHover(x, y, mask); + } + + return handled; +} + +void LLToolBarButton::onMouseEnter(S32 x, S32 y, MASK mask) +{ + LLUICtrl::onMouseEnter(x, y, mask); + + // Always highlight toolbar buttons, even if they are disabled + if (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this) + { + mNeedsHighlight = TRUE; + } + + LLToolBar* parent_toolbar = getParentByType<LLToolBar>(); + if (parent_toolbar && parent_toolbar->mButtonEnterSignal) + { + (*(parent_toolbar->mButtonEnterSignal))(this); + } +} + +void LLToolBarButton::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLButton::onMouseLeave(x, y, mask); + + LLToolBar* parent_toolbar = getParentByType<LLToolBar>(); + if (parent_toolbar && parent_toolbar->mButtonLeaveSignal) + { + (*(parent_toolbar->mButtonLeaveSignal))(this); + } +} + +void LLToolBarButton::onMouseCaptureLost() +{ + mIsDragged = false; +} + +void LLToolBarButton::onCommit() +{ + LLCommand* command = LLCommandManager::instance().getCommand(mId); + + if (!mIsEnabledSignal || (*mIsEnabledSignal)(this, command->isEnabledParameters())) + { + LLButton::onCommit(); + } +} + +void LLToolBarButton::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLButton::reshape(mWidthRange.clamp(width), height, called_from_parent); +} + +void LLToolBarButton::setEnabled(BOOL enabled) +{ + if (enabled) + { + mImageSelected = mOriginalImageSelected; + mImageUnselected = mOriginalImageUnselected; + mImagePressed = mOriginalImagePressed; + mImagePressedSelected = mOriginalImagePressedSelected; + mUnselectedLabelColor = mOriginalLabelColor; + mSelectedLabelColor = mOriginalLabelColorSelected; + mImageOverlayColor = mOriginalImageOverlayColor; + mImageOverlaySelectedColor = mOriginalImageOverlaySelectedColor; + } + else + { + mImageSelected = mImageDisabledSelected; + mImageUnselected = mImageDisabled; + mImagePressed = mImageDisabled; + mImagePressedSelected = mImageDisabledSelected; + mUnselectedLabelColor = mDisabledLabelColor; + mSelectedLabelColor = mDisabledSelectedLabelColor; + mImageOverlayColor = mImageOverlayDisabledColor; + mImageOverlaySelectedColor = mImageOverlayDisabledColor; + } +} + +const std::string LLToolBarButton::getToolTip() const +{ + std::string tooltip; + + if (labelIsTruncated() || getCurrentLabel().empty()) + { + tooltip = LLTrans::getString(LLCommandManager::instance().getCommand(mId)->labelRef()) + " -- " + LLView::getToolTip(); + } + else + { + tooltip = LLView::getToolTip(); + } + + LLToolBar* parent_toolbar = getParentByType<LLToolBar>(); + if (parent_toolbar && parent_toolbar->mButtonTooltipSuffix.length() > 0) + { + tooltip = tooltip + "\n(" + parent_toolbar->mButtonTooltipSuffix + ")"; + } + + return tooltip; +} + diff --git a/indra/llui/lltoolbar.h b/indra/llui/lltoolbar.h new file mode 100644 index 0000000000..a50c60282c --- /dev/null +++ b/indra/llui/lltoolbar.h @@ -0,0 +1,290 @@ +/** + * @file lltoolbar.h + * @author Richard Nelson + * @brief User customizable toolbar class + * + * $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_LLTOOLBAR_H +#define LL_LLTOOLBAR_H + +#include "llbutton.h" +#include "llcommandmanager.h" +#include "lllayoutstack.h" +#include "lluictrl.h" +#include "llcommandmanager.h" +#include "llassettype.h" + +class LLToolBar; +class LLToolBarButton; + +typedef boost::function<void (S32 x, S32 y, LLToolBarButton* button)> tool_startdrag_callback_t; +typedef boost::function<BOOL (S32 x, S32 y, const LLUUID& uuid, LLAssetType::EType type)> tool_handledrag_callback_t; +typedef boost::function<BOOL (void* data, S32 x, S32 y, LLToolBar* toolbar)> tool_handledrop_callback_t; + +class LLToolBarButton : public LLButton +{ + friend class LLToolBar; +public: + struct Params : public LLInitParam::Block<Params, LLButton::Params> + { + Optional<LLUI::RangeS32::Params> button_width; + Optional<S32> desired_height; + + Params() + : button_width("button_width"), + desired_height("desired_height", 20) + {} + + }; + + LLToolBarButton(const Params& p); + ~LLToolBarButton(); + + BOOL handleMouseDown(S32 x, S32 y, MASK mask); + BOOL handleHover(S32 x, S32 y, MASK mask); + + void reshape(S32 width, S32 height, BOOL called_from_parent = true); + void setEnabled(BOOL enabled); + void setCommandId(const LLCommandId& id) { mId = id; } + LLCommandId getCommandId() { return mId; } + + void setStartDragCallback(tool_startdrag_callback_t cb) { mStartDragItemCallback = cb; } + void setHandleDragCallback(tool_handledrag_callback_t cb) { mHandleDragItemCallback = cb; } + + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + void onMouseCaptureLost(); + + void onCommit(); + + virtual const std::string getToolTip() const; + +private: + void callIfEnabled(LLUICtrl::commit_callback_t commit, LLUICtrl* ctrl, const LLSD& param ); + + LLCommandId mId; + S32 mMouseDownX; + S32 mMouseDownY; + LLUI::RangeS32 mWidthRange; + S32 mDesiredHeight; + bool mIsDragged; + tool_startdrag_callback_t mStartDragItemCallback; + tool_handledrag_callback_t mHandleDragItemCallback; + + enable_signal_t* mIsEnabledSignal; + enable_signal_t* mIsRunningSignal; + enable_signal_t* mIsStartingSignal; + LLPointer<LLUIImage> mOriginalImageSelected, + mOriginalImageUnselected, + mOriginalImagePressed, + mOriginalImagePressedSelected; + LLUIColor mOriginalLabelColor, + mOriginalLabelColorSelected, + mOriginalImageOverlayColor, + mOriginalImageOverlaySelectedColor; +}; + + +namespace LLToolBarEnums +{ + enum ButtonType + { + BTNTYPE_ICONS_WITH_TEXT = 0, + BTNTYPE_ICONS_ONLY, + + BTNTYPE_COUNT + }; + + enum SideType + { + SIDE_BOTTOM, + SIDE_LEFT, + SIDE_RIGHT, + SIDE_TOP, + }; + + LLLayoutStack::ELayoutOrientation getOrientation(SideType sideType); +} + +// NOTE: This needs to occur before Param block declaration for proper compilation. +namespace LLInitParam +{ + template<> + struct TypeValues<LLToolBarEnums::ButtonType> : public TypeValuesHelper<LLToolBarEnums::ButtonType> + { + static void declareValues(); + }; + + template<> + struct TypeValues<LLToolBarEnums::SideType> : public TypeValuesHelper<LLToolBarEnums::SideType> + { + static void declareValues(); + }; +} + + +class LLToolBar +: public LLUICtrl +{ + friend class LLToolBarButton; +public: + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Mandatory<LLToolBarEnums::ButtonType> button_display_mode; + Mandatory<LLToolBarEnums::SideType> side; + + Optional<LLToolBarButton::Params> button_icon, + button_icon_and_text; + + Optional<bool> read_only, + wrap; + + Optional<S32> pad_left, + pad_top, + pad_right, + pad_bottom, + pad_between, + min_girth; + + // default command set + Multiple<LLCommandId::Params> commands; + + Optional<LLPanel::Params> button_panel; + + Params(); + }; + + // virtuals + void draw(); + void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + static const int RANK_NONE = -1; + bool addCommand(const LLCommandId& commandId, int rank = RANK_NONE); + int removeCommand(const LLCommandId& commandId); // Returns the rank the removed command was at, RANK_NONE if not found + bool hasCommand(const LLCommandId& commandId) const; // is this command bound to a button in this toolbar + bool enableCommand(const LLCommandId& commandId, bool enabled); // enable/disable button bound to the specified command, if it exists in this toolbar + bool stopCommandInProgress(const LLCommandId& commandId); // stop command if it is currently active + bool flashCommand(const LLCommandId& commandId, bool flash); // flash button associated with given command, if in this toolbar + + void setStartDragCallback(tool_startdrag_callback_t cb) { mStartDragItemCallback = cb; } // connects drag and drop behavior to external logic + void setHandleDragCallback(tool_handledrag_callback_t cb) { mHandleDragItemCallback = cb; } + void setHandleDropCallback(tool_handledrop_callback_t cb) { mHandleDropCallback = cb; } + bool isReadOnly() const { return mReadOnly; } + + LLToolBarButton* createButton(const LLCommandId& id); + + typedef boost::signals2::signal<void (LLView* button)> button_signal_t; + boost::signals2::connection setButtonAddCallback(const button_signal_t::slot_type& cb); + boost::signals2::connection setButtonEnterCallback(const button_signal_t::slot_type& cb); + boost::signals2::connection setButtonLeaveCallback(const button_signal_t::slot_type& cb); + boost::signals2::connection setButtonRemoveCallback(const button_signal_t::slot_type& cb); + + // append the specified string to end of tooltip + void setTooltipButtonSuffix(const std::string& suffix) { mButtonTooltipSuffix = suffix; } + + LLToolBarEnums::SideType getSideType() const { return mSideType; } + bool hasButtons() const { return !mButtons.empty(); } + bool isModified() const { return mModified; } + + int getRankFromPosition(S32 x, S32 y); + int getRankFromPosition(const LLCommandId& id); + + // Methods used in loading and saving toolbar settings + void setButtonType(LLToolBarEnums::ButtonType button_type); + LLToolBarEnums::ButtonType getButtonType() { return mButtonType; } + command_id_list_t& getCommandsList() { return mButtonCommands; } + void clearCommandsList(); + +private: + friend class LLUICtrlFactory; + LLToolBar(const Params&); + ~LLToolBar(); + + void initFromParams(const Params&); + void createContextMenu(); + void updateLayoutAsNeeded(); + void createButtons(); + void resizeButtonsInRow(std::vector<LLToolBarButton*>& buttons_in_row, S32 max_row_girth); + BOOL isSettingChecked(const LLSD& userdata); + void onSettingEnable(const LLSD& userdata); + void onRemoveSelectedCommand(); + +private: + // static layout state + const bool mReadOnly; + const LLToolBarEnums::SideType mSideType; + const bool mWrap; + const S32 mPadLeft, + mPadRight, + mPadTop, + mPadBottom, + mPadBetween, + mMinGirth; + + // drag and drop state + tool_startdrag_callback_t mStartDragItemCallback; + tool_handledrag_callback_t mHandleDragItemCallback; + tool_handledrop_callback_t mHandleDropCallback; + bool mDragAndDropTarget; + int mDragRank; + S32 mDragx, + mDragy, + mDragGirth; + + typedef std::list<LLToolBarButton*> toolbar_button_list; + typedef std::map<LLUUID, LLToolBarButton*> command_id_map; + toolbar_button_list mButtons; + command_id_list_t mButtonCommands; + command_id_map mButtonMap; + + LLToolBarEnums::ButtonType mButtonType; + LLToolBarButton::Params mButtonParams[LLToolBarEnums::BTNTYPE_COUNT]; + + // related widgets + LLLayoutStack* mCenteringStack; + LLPanel* mButtonPanel; + LLHandle<class LLContextMenu> mPopupMenuHandle; + LLHandle<class LLView> mRemoveButtonHandle; + + LLToolBarButton* mRightMouseTargetButton; + + bool mNeedsLayout; + bool mModified; + + button_signal_t* mButtonAddSignal; + button_signal_t* mButtonEnterSignal; + button_signal_t* mButtonLeaveSignal; + button_signal_t* mButtonRemoveSignal; + + std::string mButtonTooltipSuffix; +}; + + +#endif // LL_LLTOOLBAR_H diff --git a/indra/llui/lltooltip.cpp b/indra/llui/lltooltip.cpp index 6390039794..f737d48abf 100644 --- a/indra/llui/lltooltip.cpp +++ b/indra/llui/lltooltip.cpp @@ -55,7 +55,7 @@ static LLDefaultChildRegistry::Register<LLToolTipView> register_tooltip_view("to LLToolTipView::Params::Params() { - mouse_opaque = false; + changeDefault(mouse_opaque, false); } LLToolTipView::LLToolTipView(const LLToolTipView::Params& p) @@ -156,7 +156,7 @@ LLToolTip::Params::Params() web_based_media("web_based_media", false), media_playing("media_playing", false) { - chrome = true; + changeDefault(chrome, true); } LLToolTip::LLToolTip(const LLToolTip::Params& p) @@ -180,6 +180,7 @@ LLToolTip::LLToolTip(const LLToolTip::Params& p) params.font = p.font; params.use_ellipses = true; params.wrap = p.wrap; + params.font_valign = LLFontGL::VCENTER; params.parse_urls = false; // disallow hyperlinks in tooltips, as they want to spawn their own explanatory tooltips mTextBox = LLUICtrlFactory::create<LLTextBox> (params); addChild(mTextBox); @@ -190,7 +191,6 @@ LLToolTip::LLToolTip(const LLToolTip::Params& p) { LLButton::Params icon_params; icon_params.name = "tooltip_info"; - icon_params.label(""); // provid label but set to empty so name does not overwrite it -angela LLRect icon_rect; LLUIImage* imagep = p.image; TOOLTIP_ICON_SIZE = (imagep ? imagep->getWidth() : 16); @@ -200,7 +200,7 @@ LLToolTip::LLToolTip(const LLToolTip::Params& p) icon_params.image_selected(imagep); icon_params.scale_image(true); - icon_params.flash_color(icon_params.highlight_color()); + icon_params.flash_color.control = "ButtonUnselectedFgColor"; mInfoButton = LLUICtrlFactory::create<LLButton>(icon_params); if (p.click_callback.isProvided()) { @@ -291,6 +291,12 @@ void LLToolTip::initFromParams(const LLToolTip::Params& p) S32 text_width = llmin(p.max_width(), mTextBox->getTextPixelWidth()); S32 text_height = mTextBox->getTextPixelHeight(); mTextBox->reshape(text_width, text_height); + if (mInfoButton) + { + LLRect text_rect = mTextBox->getRect(); + LLRect icon_rect = mInfoButton->getRect(); + mTextBox->translate(0, icon_rect.getCenterY() - text_rect.getCenterY()); + } // reshape tooltip panel to fit text box LLRect tooltip_rect = calcBoundingRect(); @@ -299,6 +305,8 @@ void LLToolTip::initFromParams(const LLToolTip::Params& p) tooltip_rect.mBottom = 0; tooltip_rect.mLeft = 0; + mTextBox->reshape(mTextBox->getRect().getWidth(), llmax(mTextBox->getRect().getHeight(), tooltip_rect.getHeight() - 2 * mPadding)); + setShape(tooltip_rect); } @@ -402,12 +410,12 @@ void LLToolTipMgr::createToolTip(const LLToolTip::Params& params) LLToolTip::Params tooltip_params(params); // block mouse events if there is a click handler registered (specifically, hover) - if (params.click_callback.isProvided()) + if (params.click_callback.isProvided() && !params.mouse_opaque.isProvided()) { // set mouse_opaque to true if it wasn't already set to something else // this prevents mouse down from going "through" the tooltip and ultimately // causing the tooltip to disappear - tooltip_params.mouse_opaque.setIfNotProvided(true); + tooltip_params.mouse_opaque = true; } tooltip_params.rect = LLRect (0, 1, 1, 0); diff --git a/indra/llui/lltransutil.cpp b/indra/llui/lltransutil.cpp index 9d0ff9d5cb..58fa8a0828 100644 --- a/indra/llui/lltransutil.cpp +++ b/indra/llui/lltransutil.cpp @@ -26,10 +26,11 @@ #include "linden_common.h" +#include "lltransutil.h" + #include "lltrans.h" #include "lluictrlfactory.h" - -#include "lltransutil.h" +#include "llxmlnode.h" bool LLTransUtil::parseStrings(const std::string& xml_filename, const std::set<std::string>& default_args) diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index 19c42bf61a..b5e27616b7 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -41,6 +41,7 @@ #include "llgl.h" // Project includes +#include "llcommandmanager.h" #include "llcontrol.h" #include "llui.h" #include "lluicolortable.h" @@ -57,10 +58,13 @@ #include "llfiltereditor.h" #include "llflyoutbutton.h" #include "llsearcheditor.h" +#include "lltoolbar.h" // for XUIParse #include "llquaternion.h" #include <boost/tokenizer.hpp> +#include <boost/algorithm/string/find_iterator.hpp> +#include <boost/algorithm/string/finder.hpp> // // Globals @@ -85,14 +89,14 @@ std::list<std::string> gUntranslated; /*static*/ LLUI::remove_popup_t LLUI::sRemovePopupFunc; /*static*/ LLUI::clear_popups_t LLUI::sClearPopupsFunc; -// register filtereditor here +// register filter editor here static LLDefaultChildRegistry::Register<LLFilterEditor> register_filter_editor("filter_editor"); static LLDefaultChildRegistry::Register<LLFlyoutButton> register_flyout_button("flyout_button"); static LLDefaultChildRegistry::Register<LLSearchEditor> register_search_editor("search_editor"); // register other widgets which otherwise may not be linked in static LLDefaultChildRegistry::Register<LLLoadingIndicator> register_loading_indicator("loading_indicator"); - +static LLDefaultChildRegistry::Register<LLToolBar> register_toolbar("toolbar"); // // Functions @@ -102,7 +106,7 @@ void make_ui_sound(const char* namep) std::string name = ll_safe_string(namep); if (!LLUI::sSettingGroups["config"]->controlExists(name)) { - llwarns << "tried to make ui sound for unknown sound name: " << name << llendl; + llwarns << "tried to make UI sound for unknown sound name: " << name << llendl; } else { @@ -113,12 +117,12 @@ void make_ui_sound(const char* namep) { if (LLUI::sSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) { - llinfos << "ui sound name: " << name << " triggered but silent (null uuid)" << llendl; + llinfos << "UI sound name: " << name << " triggered but silent (null uuid)" << llendl; } } else { - llwarns << "ui sound named: " << name << " does not translate to a valid uuid" << llendl; + llwarns << "UI sound named: " << name << " does not translate to a valid uuid" << llendl; } } @@ -126,7 +130,7 @@ void make_ui_sound(const char* namep) { if (LLUI::sSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) { - llinfos << "ui sound name: " << name << llendl; + llinfos << "UI sound name: " << name << llendl; } LLUI::sAudioCallback(uuid); } @@ -149,11 +153,11 @@ void gl_state_for_2d(S32 width, S32 height) F32 window_width = (F32) width;//gViewerWindow->getWindowWidth(); F32 window_height = (F32) height;//gViewerWindow->getWindowHeight(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.0f, llmax(window_width, 1.f), 0.0f, llmax(window_height,1.f), -1.0f, 1.0f); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.loadIdentity(); + gGL.ortho(0.0f, llmax(window_width, 1.f), 0.0f, llmax(window_height,1.f), -1.0f, 1.0f); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.loadIdentity(); stop_glerror(); } @@ -470,7 +474,7 @@ void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLTex return; } - // add in offset of current image to current ui translation + // add in offset of current image to current UI translation const LLVector3 ui_scale = gGL.getUIScale(); const LLVector3 ui_translation = (gGL.getUITranslation() + LLVector3(x, y, 0.f)).scaledVec(ui_scale); @@ -522,11 +526,18 @@ void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLTex if (solid_color) { - gGL.getTexUnit(0)->setTextureColorBlend(LLTexUnit::TBO_REPLACE, LLTexUnit::TBS_PREV_COLOR); - gGL.getTexUnit(0)->setTextureAlphaBlend(LLTexUnit::TBO_MULT, LLTexUnit::TBS_TEX_ALPHA, LLTexUnit::TBS_VERT_ALPHA); + if (LLGLSLShader::sNoFixedFunction) + { + gSolidColorProgram.bind(); + } + else + { + gGL.getTexUnit(0)->setTextureColorBlend(LLTexUnit::TBO_REPLACE, LLTexUnit::TBS_PREV_COLOR); + gGL.getTexUnit(0)->setTextureAlphaBlend(LLTexUnit::TBO_MULT, LLTexUnit::TBS_TEX_ALPHA, LLTexUnit::TBS_VERT_ALPHA); + } } - gGL.getTexUnit(0)->bind(image); + gGL.getTexUnit(0)->bind(image, true); gGL.color4fv(color.mV); @@ -697,7 +708,14 @@ void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLTex if (solid_color) { - gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT); + if (LLGLSLShader::sNoFixedFunction) + { + gUIProgram.bind(); + } + else + { + gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT); + } } } @@ -717,7 +735,7 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->bind(image); + gGL.getTexUnit(0)->bind(image, true); gGL.color4fv(color.mV); @@ -770,7 +788,7 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre LLMatrix3 quat(0.f, 0.f, degrees*DEG_TO_RAD); - gGL.getTexUnit(0)->bind(image); + gGL.getTexUnit(0)->bind(image, true); gGL.color4fv(color.mV); @@ -937,10 +955,12 @@ void gl_ring( F32 radius, F32 width, const LLColor4& center_color, const LLColor if( render_center ) { gGL.color4fv(center_color.mV); + gGL.diffuseColor4fv(center_color.mV); gl_deep_circle( radius, width, steps ); } else { + gGL.diffuseColor4fv(side_color.mV); gl_washer_2d(radius, radius - width, steps, side_color, side_color); gGL.translateUI(0.f, 0.f, width); gl_washer_2d(radius - width, radius, steps, side_color, side_color); @@ -950,37 +970,55 @@ void gl_ring( F32 radius, F32 width, const LLColor4& center_color, const LLColor } // Draw gray and white checkerboard with black border -void gl_rect_2d_checkerboard(const LLRect& rect) +void gl_rect_2d_checkerboard(const LLRect& rect, GLfloat alpha) { - // Initialize the first time this is called. - const S32 PIXELS = 32; - static GLubyte checkerboard[PIXELS * PIXELS]; - static BOOL first = TRUE; - if( first ) - { - for( S32 i = 0; i < PIXELS; i++ ) + if (!LLGLSLShader::sNoFixedFunction) + { + // Initialize the first time this is called. + const S32 PIXELS = 32; + static GLubyte checkerboard[PIXELS * PIXELS]; + static BOOL first = TRUE; + if( first ) { - for( S32 j = 0; j < PIXELS; j++ ) + for( S32 i = 0; i < PIXELS; i++ ) { - checkerboard[i * PIXELS + j] = ((i & 1) ^ (j & 1)) * 0xFF; + for( S32 j = 0; j < PIXELS; j++ ) + { + checkerboard[i * PIXELS + j] = ((i & 1) ^ (j & 1)) * 0xFF; + } } + first = FALSE; } - first = FALSE; - } - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - // ...white squares - gGL.color3f( 1.f, 1.f, 1.f ); - gl_rect_2d(rect); + // ...white squares + gGL.color4f( 1.f, 1.f, 1.f, alpha ); + gl_rect_2d(rect); - // ...gray squares - gGL.color3f( .7f, .7f, .7f ); - gGL.flush(); - glPolygonStipple( checkerboard ); + // ...gray squares + gGL.color4f( .7f, .7f, .7f, alpha ); + gGL.flush(); + + glPolygonStipple( checkerboard ); + + LLGLEnable polygon_stipple(GL_POLYGON_STIPPLE); + gl_rect_2d(rect); + } + else + { //polygon stipple is deprecated, use "Checker" texture + LLPointer<LLUIImage> img = LLUI::getUIImage("Checker"); + gGL.getTexUnit(0)->bind(img->getImage()); + gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_WRAP); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); - LLGLEnable polygon_stipple(GL_POLYGON_STIPPLE); - gl_rect_2d(rect); + LLColor4 color(1.f, 1.f, 1.f, alpha); + LLRectf uv_rect(0, 0, rect.getWidth()/32.f, rect.getHeight()/32.f); + + gl_draw_scaled_image(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), + img->getImage(), color, uv_rect); + } + gGL.flush(); } @@ -1596,34 +1634,40 @@ void LLUI::initClass(const settings_map_t& settings, sWindow = NULL; // set later in startup LLFontGL::sShadowColor = LLUIColorTable::instance().getColor("ColorDropShadow"); - // Callbacks for associating controls with floater visibilty: - LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Floater.Toggle", boost::bind(&LLFloaterReg::toggleFloaterInstance, _2)); - LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Floater.Show", boost::bind(&LLFloaterReg::showFloaterInstance, _2)); - LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Floater.Hide", boost::bind(&LLFloaterReg::hideFloaterInstance, _2)); - LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Floater.InitToVisibilityControl", boost::bind(&LLFloaterReg::initUICtrlToFloaterVisibilityControl, _1, _2)); + LLUICtrl::CommitCallbackRegistry::Registrar& reg = LLUICtrl::CommitCallbackRegistry::defaultRegistrar(); + + // Callbacks for associating controls with floater visibility: + reg.add("Floater.Toggle", boost::bind(&LLFloaterReg::toggleInstance, _2, LLSD())); + reg.add("Floater.ToggleOrBringToFront", boost::bind(&LLFloaterReg::toggleInstanceOrBringToFront, _2, LLSD())); + reg.add("Floater.Show", boost::bind(&LLFloaterReg::showInstance, _2, LLSD(), FALSE)); + reg.add("Floater.Hide", boost::bind(&LLFloaterReg::hideInstance, _2, LLSD())); // Button initialization callback for toggle buttons - LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Button.SetFloaterToggle", boost::bind(&LLButton::setFloaterToggle, _1, _2)); + reg.add("Button.SetFloaterToggle", boost::bind(&LLButton::setFloaterToggle, _1, _2)); - // Button initialization callback for toggle buttons on dockale floaters - LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Button.SetDockableFloaterToggle", boost::bind(&LLButton::setDockableFloaterToggle, _1, _2)); + // Button initialization callback for toggle buttons on dockable floaters + reg.add("Button.SetDockableFloaterToggle", boost::bind(&LLButton::setDockableFloaterToggle, _1, _2)); // Display the help topic for the current context - LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Button.ShowHelp", boost::bind(&LLButton::showHelp, _1, _2)); + reg.add("Button.ShowHelp", boost::bind(&LLButton::showHelp, _1, _2)); // Currently unused, but kept for reference: - LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Button.ToggleFloater", boost::bind(&LLButton::toggleFloaterAndSetToggleState, _1, _2)); + reg.add("Button.ToggleFloater", boost::bind(&LLButton::toggleFloaterAndSetToggleState, _1, _2)); - // Used by menus along with Floater.Toggle to display visibility as a checkmark - LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.Visible", boost::bind(&LLFloaterReg::floaterInstanceVisible, _2)); + // Used by menus along with Floater.Toggle to display visibility as a check-mark + LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.Visible", boost::bind(&LLFloaterReg::instanceVisible, _2, LLSD())); + LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.IsOpen", boost::bind(&LLFloaterReg::instanceVisible, _2, LLSD())); + + // Parse the master list of commands + LLCommandManager::load(); } void LLUI::cleanupClass() { if(sImageProvider) { - sImageProvider->cleanUp(); - } + sImageProvider->cleanUp(); +} } void LLUI::setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t& remove_popup, const clear_popups_t& clear_popups) @@ -1654,21 +1698,22 @@ void LLUI::translate(F32 x, F32 y, F32 z) gGL.translateUI(x,y,z); LLFontGL::sCurOrigin.mX += (S32) x; LLFontGL::sCurOrigin.mY += (S32) y; - LLFontGL::sCurOrigin.mZ += z; + LLFontGL::sCurDepth += z; } //static void LLUI::pushMatrix() { gGL.pushUIMatrix(); - LLFontGL::sOriginStack.push_back(LLFontGL::sCurOrigin); + LLFontGL::sOriginStack.push_back(std::make_pair(LLFontGL::sCurOrigin, LLFontGL::sCurDepth)); } //static void LLUI::popMatrix() { gGL.popUIMatrix(); - LLFontGL::sCurOrigin = *LLFontGL::sOriginStack.rbegin(); + LLFontGL::sCurOrigin = LLFontGL::sOriginStack.back().first; + LLFontGL::sCurDepth = LLFontGL::sOriginStack.back().second; LLFontGL::sOriginStack.pop_back(); } @@ -1678,7 +1723,7 @@ void LLUI::loadIdentity() gGL.loadUIIdentity(); LLFontGL::sCurOrigin.mX = 0; LLFontGL::sCurOrigin.mY = 0; - LLFontGL::sCurOrigin.mZ = 0; + LLFontGL::sCurDepth = 0.f; } //static @@ -1701,10 +1746,7 @@ void LLUI::setMousePositionScreen(S32 x, S32 y) screen_x = llround((F32)x * sGLScaleFactor.mV[VX]); screen_y = llround((F32)y * sGLScaleFactor.mV[VY]); - LLCoordWindow window_point; - LLView::getWindow()->convertCoords(LLCoordGL(screen_x, screen_y), &window_point); - - LLView::getWindow()->setCursorPosition(window_point); + LLView::getWindow()->setCursorPosition(LLCoordGL(screen_x, screen_y).convert()); } //static @@ -1712,8 +1754,7 @@ void LLUI::getMousePositionScreen(S32 *x, S32 *y) { LLCoordWindow cursor_pos_window; getWindow()->getCursorPosition(&cursor_pos_window); - LLCoordGL cursor_pos_gl; - getWindow()->convertCoords(cursor_pos_window, &cursor_pos_gl); + LLCoordGL cursor_pos_gl(cursor_pos_window.convert()); *x = llround((F32)cursor_pos_gl.mX / sGLScaleFactor.mV[VX]); *y = llround((F32)cursor_pos_gl.mY / sGLScaleFactor.mV[VX]); } @@ -1799,9 +1840,12 @@ void LLUI::setupPaths() LLXMLNodePtr root; BOOL success = LLXMLNode::parseFile(filename, root, NULL); Paths paths; - LLXUIParser parser; - parser.readXUI(root, paths, filename); + if(success) + { + LLXUIParser parser; + parser.readXUI(root, paths, filename); + } sXUIPaths.clear(); if (success && paths.validateBlock()) @@ -2009,13 +2053,60 @@ void LLUI::positionViewNearMouse(LLView* view, S32 spawn_x, S32 spawn_y) CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2); S32 local_x, local_y; - // convert screen coordinates to tooltipview-local coordinates + // convert screen coordinates to tooltip view-local coordinates parent->screenPointToLocal(spawn_x, spawn_y, &local_x, &local_y); // Start at spawn position (using left/top) view->setOrigin( local_x, local_y - view->getRect().getHeight()); - // Make sure we're onscreen and not overlapping the mouse - view->translateIntoRectWithExclusion( virtual_window_rect, mouse_rect, FALSE ); + // Make sure we're on-screen and not overlapping the mouse + view->translateIntoRectWithExclusion( virtual_window_rect, mouse_rect ); +} + +LLView* LLUI::resolvePath(LLView* context, const std::string& path) +{ + // Nothing about resolvePath() should require non-const LLView*. If caller + // wants non-const, call the const flavor and then cast away const-ness. + return const_cast<LLView*>(resolvePath(const_cast<const LLView*>(context), path)); +} + +const LLView* LLUI::resolvePath(const LLView* context, const std::string& path) +{ + // Create an iterator over slash-separated parts of 'path'. Dereferencing + // this iterator returns an iterator_range over the substring. Unlike + // LLStringUtil::getTokens(), this split_iterator doesn't combine adjacent + // delimiters: leading/trailing slash produces an empty substring, double + // slash produces an empty substring. That's what we need. + boost::split_iterator<std::string::const_iterator> ti(path, boost::first_finder("/")), tend; + + if (ti == tend) + { + // 'path' is completely empty, no navigation + return context; + } + + // leading / means "start at root" + if (ti->empty()) + { + context = getRootView(); + ++ti; + } + + bool recurse = false; + for (; ti != tend && context; ++ti) + { + if (ti->empty()) + { + recurse = true; + } + else + { + std::string part(ti->begin(), ti->end()); + context = context->findChildView(part, recurse); + recurse = false; + } + } + + return context; } @@ -2023,46 +2114,37 @@ void LLUI::positionViewNearMouse(LLView* view, S32 spawn_x, S32 spawn_y) namespace LLInitParam { - TypedParam<LLUIColor >::TypedParam(BlockDescriptor& descriptor, const char* name, const LLUIColor& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, name, value, func, min_count, max_count), + ParamValue<LLUIColor, TypeValues<LLUIColor> >::ParamValue(const LLUIColor& color) + : super_t(color), red("red"), green("green"), blue("blue"), alpha("alpha"), control("") { - setBlockFromValue(); + updateBlockFromValue(false); } - void TypedParam<LLUIColor>::setValueFromBlock() const + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateValueFromBlock() { - if (control.isProvided()) + if (control.isProvided() && !control().empty()) { - mData.mValue = LLUIColorTable::instance().getColor(control); + updateValue(LLUIColorTable::instance().getColor(control)); } else { - mData.mValue = LLColor4(red, green, blue, alpha); + updateValue(LLColor4(red, green, blue, alpha)); } } - void TypedParam<LLUIColor>::setBlockFromValue() - { - LLColor4 color = mData.mValue.get(); - red.set(color.mV[VRED], false); - green.set(color.mV[VGREEN], false); - blue.set(color.mV[VBLUE], false); - alpha.set(color.mV[VALPHA], false); - control.set("", false); - } - - void TypeValues<LLUIColor>::declareValues() + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateBlockFromValue(bool make_block_authoritative) { - declare("white", LLColor4::white); - declare("black", LLColor4::black); - declare("red", LLColor4::red); - declare("green", LLColor4::green); - declare("blue", LLColor4::blue); + LLColor4 color = getValue(); + red.set(color.mV[VRED], make_block_authoritative); + green.set(color.mV[VGREEN], make_block_authoritative); + blue.set(color.mV[VBLUE], make_block_authoritative); + alpha.set(color.mV[VALPHA], make_block_authoritative); + control.set("", make_block_authoritative); } bool ParamCompare<const LLFontGL*, false>::equals(const LLFontGL* a, const LLFontGL* b) @@ -2071,23 +2153,26 @@ namespace LLInitParam && !(b->getFontDesc() < a->getFontDesc()); } - TypedParam<const LLFontGL*>::TypedParam(BlockDescriptor& descriptor, const char* _name, const LLFontGL*const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, _name, value, func, min_count, max_count), + ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::ParamValue(const LLFontGL* fontp) + : super_t(fontp), name("name"), size("size"), style("style") { - setBlockFromValue(); + if (!fontp) + { + updateValue(LLFontGL::getFontDefault()); + } addSynonym(name, ""); - setBlockFromValue(); + updateBlockFromValue(false); } - void TypedParam<const LLFontGL*>::setValueFromBlock() const + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateValueFromBlock() { const LLFontGL* res_fontp = LLFontGL::getFontByName(name); if (res_fontp) { - mData.mValue = res_fontp; + updateValue(res_fontp); return; } @@ -2097,22 +2182,26 @@ namespace LLInitParam const LLFontGL* fontp = LLFontGL::getFont(desc); if (fontp) { - mData.mValue = fontp; - } + updateValue(fontp); + } + else + { + updateValue(LLFontGL::getFontDefault()); + } } - void TypedParam<const LLFontGL*>::setBlockFromValue() + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateBlockFromValue(bool make_block_authoritative) { - if (mData.mValue) + if (getValue()) { - name.set(LLFontGL::nameFromFont(mData.mValue), false); - size.set(LLFontGL::sizeFromFont(mData.mValue), false); - style.set(LLFontGL::getStringFromStyle(mData.mValue->getFontDesc().getStyle()), false); + name.set(LLFontGL::nameFromFont(getValue()), make_block_authoritative); + size.set(LLFontGL::sizeFromFont(getValue()), make_block_authoritative); + style.set(LLFontGL::getStringFromStyle(getValue()->getFontDesc().getStyle()), make_block_authoritative); } } - TypedParam<LLRect>::TypedParam(BlockDescriptor& descriptor, const char* name, const LLRect& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, name, value, func, min_count, max_count), + ParamValue<LLRect, TypeValues<LLRect> >::ParamValue(const LLRect& rect) + : super_t(rect), left("left"), top("top"), right("right"), @@ -2120,10 +2209,10 @@ namespace LLInitParam width("width"), height("height") { - setBlockFromValue(); + updateBlockFromValue(false); } - void TypedParam<LLRect>::setValueFromBlock() const + void ParamValue<LLRect, TypeValues<LLRect> >::updateValueFromBlock() { LLRect rect; @@ -2184,40 +2273,43 @@ namespace LLInitParam rect.mBottom = bottom; rect.mTop = top; } - mData.mValue = rect; + updateValue(rect); } - void TypedParam<LLRect>::setBlockFromValue() + void ParamValue<LLRect, TypeValues<LLRect> >::updateBlockFromValue(bool make_block_authoritative) { // because of the ambiguity in specifying a rect by position and/or dimensions - // we clear the "provided" flag so that values from xui/etc have priority - // over those calculated from the rect object + // we use the lowest priority pairing so that any valid pairing in xui + // will override those calculated from the rect object + // in this case, that is left+width and bottom+height + LLRect& value = getValue(); + + right.set(value.mRight, false); + left.set(value.mLeft, make_block_authoritative); + width.set(value.getWidth(), make_block_authoritative); - left.set(mData.mValue.mLeft, false); - right.set(mData.mValue.mRight, false); - bottom.set(mData.mValue.mBottom, false); - top.set(mData.mValue.mTop, false); - width.set(mData.mValue.getWidth(), false); - height.set(mData.mValue.getHeight(), false); + top.set(value.mTop, false); + bottom.set(value.mBottom, make_block_authoritative); + height.set(value.getHeight(), make_block_authoritative); } - TypedParam<LLCoordGL>::TypedParam(BlockDescriptor& descriptor, const char* name, LLCoordGL value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, name, value, func, min_count, max_count), + ParamValue<LLCoordGL, TypeValues<LLCoordGL> >::ParamValue(const LLCoordGL& coord) + : super_t(coord), x("x"), y("y") { - setBlockFromValue(); + updateBlockFromValue(false); } - void TypedParam<LLCoordGL>::setValueFromBlock() const + void ParamValue<LLCoordGL, TypeValues<LLCoordGL> >::updateValueFromBlock() { - mData.mValue.set(x, y); + updateValue(LLCoordGL(x, y)); } - void TypedParam<LLCoordGL>::setBlockFromValue() + void ParamValue<LLCoordGL, TypeValues<LLCoordGL> >::updateBlockFromValue(bool make_block_authoritative) { - x.set(mData.mValue.mX, false); - y.set(mData.mValue.mY, false); + x.set(getValue().mX, make_block_authoritative); + y.set(getValue().mY, make_block_authoritative); } diff --git a/indra/llui/llui.h b/indra/llui/llui.h index fc545c85d5..28e84fa444 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -33,16 +33,15 @@ #include "llrect.h" #include "llcontrol.h" #include "llcoord.h" -#include "lluiimage.h" // *TODO: break this dependency, need to add #include "lluiimage.h" to all widgets that hold an Optional<LLUIImage*> in their paramblocks +#include "llglslshader.h" #include "llinitparam.h" #include "llregistry.h" #include "lluicolor.h" #include "lluicolortable.h" #include <boost/signals2.hpp> #include "lllazyvalue.h" -#include "llhandle.h" // *TODO: remove this dependency, added as a - // convenience when LLHandle moved to llhandle.h #include "llframetimer.h" +#include <limits> // LLUIFactory #include "llsd.h" @@ -50,6 +49,7 @@ // for initparam specialization #include "llfontgl.h" + class LLColor4; class LLVector3; class LLVector2; @@ -79,7 +79,7 @@ void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, const LL void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, S32 pixel_offset = 0, BOOL filled = TRUE ); void gl_rect_2d(const LLRect& rect, BOOL filled = TRUE ); void gl_rect_2d(const LLRect& rect, const LLColor4& color, BOOL filled = TRUE ); -void gl_rect_2d_checkerboard(const LLRect& rect); +void gl_rect_2d_checkerboard(const LLRect& rect, GLfloat alpha = 1.0f); void gl_drop_shadow(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &start_color, S32 lines); @@ -149,6 +149,122 @@ class LLUI LOG_CLASS(LLUI); public: // + // Classes + // + + struct RangeS32 + { + struct Params : public LLInitParam::Block<Params> + { + Optional<S32> minimum, + maximum; + + Params() + : minimum("min", 0), + maximum("max", S32_MAX) + {} + }; + + // correct for inverted params + RangeS32(const Params& p = Params()) + : mMin(p.minimum), + mMax(p.maximum) + { + sanitizeRange(); + } + + RangeS32(S32 minimum, S32 maximum) + : mMin(minimum), + mMax(maximum) + { + sanitizeRange(); + } + + S32 clamp(S32 input) + { + if (input < mMin) return mMin; + if (input > mMax) return mMax; + return input; + } + + void setRange(S32 minimum, S32 maximum) + { + mMin = minimum; + mMax = maximum; + sanitizeRange(); + } + + S32 getMin() { return mMin; } + S32 getMax() { return mMax; } + + bool operator==(const RangeS32& other) const + { + return mMin == other.mMin + && mMax == other.mMax; + } + private: + void sanitizeRange() + { + if (mMin > mMax) + { + llwarns << "Bad interval range (" << mMin << ", " << mMax << ")" << llendl; + // since max is usually the most dangerous one to ignore (buffer overflow, etc), prefer it + // in the case of a malformed range + mMin = mMax; + } + } + + + S32 mMin, + mMax; + }; + + struct ClampedS32 : public RangeS32 + { + struct Params : public LLInitParam::Block<Params, RangeS32::Params> + { + Mandatory<S32> value; + + Params() + : value("", 0) + { + addSynonym(value, "value"); + } + }; + + ClampedS32(const Params& p) + : RangeS32(p) + {} + + ClampedS32(const RangeS32& range) + : RangeS32(range) + { + // set value here, after range has been sanitized + mValue = clamp(0); + } + + ClampedS32(S32 value, const RangeS32& range = RangeS32()) + : RangeS32(range) + { + mValue = clamp(value); + } + + S32 get() + { + return mValue; + } + + void set(S32 value) + { + mValue = clamp(value); + } + + + private: + S32 mValue; + }; + + // // Methods // typedef std::map<std::string, LLControlGroup*> settings_map_t; @@ -185,6 +301,33 @@ public: //helper functions (should probably move free standing rendering helper functions here) static LLView* getRootView() { return sRootView; } static void setRootView(LLView* view) { sRootView = view; } + /** + * Walk the LLView tree to resolve a path + * Paths can be discovered using Develop > XUI > Show XUI Paths + * + * A leading "/" indicates the root of the tree is the starting + * position of the search, (otherwise the context node is used) + * + * Adjacent "//" mean that the next level of the search is done + * recursively ("descendant" rather than "child"). + * + * Return values: If no match is found, NULL is returned, + * otherwise the matching LLView* is returned. + * + * Examples: + * + * "/" -> return the root view + * "/foo" -> find "foo" as a direct child of the root + * "foo" -> find "foo" as a direct child of the context node + * "//foo" -> find the first "foo" child anywhere in the tree + * "/foo/bar" -> find "foo" as direct child of the root, and + * "bar" as a direct child of "foo" + * "//foo//bar/baz" -> find the first "foo" anywhere in the + * tree, the first "bar" anywhere under it, and "baz" + * as a direct child of that + */ + static const LLView* resolvePath(const LLView* context, const std::string& path); + static LLView* resolvePath(LLView* context, const std::string& path); static std::string locateSkin(const std::string& filename); static void setMousePositionScreen(S32 x, S32 y); static void getMousePositionScreen(S32 *x, S32 *y); @@ -238,8 +381,6 @@ private: // Moved LLLocalClipRect to lllocalcliprect.h -// Moved all LLHandle-related code to llhandle.h - //RN: maybe this needs to moved elsewhere? class LLImageProviderInterface { @@ -341,7 +482,7 @@ template <typename T> LLRegisterWith<LLInitClassList> LLInitClass<T>::sRegister( template <typename T> LLRegisterWith<LLDestroyClassList> LLDestroyClass<T>::sRegister(&T::destroyClass); // useful parameter blocks -struct TimeIntervalParam : public LLInitParam::Choice<TimeIntervalParam> +struct TimeIntervalParam : public LLInitParam::ChoiceBlock<TimeIntervalParam> { Alternative<F32> seconds; Alternative<S32> frames; @@ -371,10 +512,10 @@ public: namespace LLInitParam { template<> - class TypedParam<LLRect> - : public BlockValue<LLRect> + class ParamValue<LLRect, TypeValues<LLRect> > + : public CustomParamValue<LLRect> { - typedef BlockValue<LLRect> super_t; + typedef CustomParamValue<LLRect> super_t; public: Optional<S32> left, top, @@ -383,62 +524,43 @@ namespace LLInitParam width, height; - TypedParam(BlockDescriptor& descriptor, const char* name, const LLRect& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); + ParamValue(const LLRect& value); - void setValueFromBlock() const; - void setBlockFromValue(); + void updateValueFromBlock(); + void updateBlockFromValue(bool make_block_authoritative); }; template<> - struct TypeValues<LLUIColor> : public TypeValuesHelper<LLUIColor> + class ParamValue<LLUIColor, TypeValues<LLUIColor> > + : public CustomParamValue<LLUIColor> { - static void declareValues(); - }; + typedef CustomParamValue<LLUIColor> super_t; - template<> - class TypedParam<LLUIColor> - : public BlockValue<LLUIColor> - { - typedef BlockValue<LLUIColor> super_t; public: - Optional<F32> red, - green, - blue, - alpha; - Optional<std::string> control; - - TypedParam(BlockDescriptor& descriptor, const char* name, const LLUIColor& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); - void setValueFromBlock() const; - void setBlockFromValue(); + Optional<F32> red, + green, + blue, + alpha; + Optional<std::string> control; + + ParamValue(const LLUIColor& color); + void updateValueFromBlock(); + void updateBlockFromValue(bool make_block_authoritative); }; - // provide a better default for Optional<const LLFontGL*> than NULL - template <> - struct DefaultInitializer<const LLFontGL*> - { - // return reference to a single default instance of T - // built-in types will be initialized to zero, default constructor otherwise - static const LLFontGL* get() - { - static const LLFontGL* sDefaultFont = LLFontGL::getFontDefault(); - return sDefaultFont; - } - }; - - template<> - class TypedParam<const LLFontGL*> - : public BlockValue<const LLFontGL*> + class ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> > + : public CustomParamValue<const LLFontGL* > { - typedef BlockValue<const LLFontGL*> super_t; + typedef CustomParamValue<const LLFontGL*> super_t; public: Optional<std::string> name, size, style; - TypedParam(BlockDescriptor& descriptor, const char* name, const LLFontGL* const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); - void setValueFromBlock() const; - void setBlockFromValue(); + ParamValue(const LLFontGL* value); + void updateValueFromBlock(); + void updateBlockFromValue(bool make_block_authoritative); }; template<> @@ -467,18 +589,21 @@ namespace LLInitParam template<> - class TypedParam<LLCoordGL> - : public BlockValue<LLCoordGL> + class ParamValue<LLCoordGL, TypeValues<LLCoordGL> > + : public CustomParamValue<LLCoordGL> { - typedef BlockValue<LLCoordGL> super_t; + typedef CustomParamValue<LLCoordGL> super_t; public: Optional<S32> x, y; - TypedParam(BlockDescriptor& descriptor, const char* name, LLCoordGL value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); - void setValueFromBlock() const; - void setBlockFromValue(); + ParamValue(const LLCoordGL& val); + void updateValueFromBlock(); + void updateBlockFromValue(bool make_block_authoritative); }; } +extern LLGLSLShader gSolidColorProgram; +extern LLGLSLShader gUIProgram; + #endif diff --git a/indra/llui/lluicolortable.cpp b/indra/llui/lluicolortable.cpp index 0641f6d175..9455d09cc0 100644 --- a/indra/llui/lluicolortable.cpp +++ b/indra/llui/lluicolortable.cpp @@ -215,6 +215,12 @@ bool LLUIColorTable::loadFromSettings() result |= loadFromFilename(current_filename, mLoadedColors); } + current_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SKIN, "colors.xml"); + if(current_filename != default_filename) + { + result |= loadFromFilename(current_filename, mLoadedColors); + } + std::string user_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "colors.xml"); loadFromFilename(user_filename, mUserSetColors); diff --git a/indra/llui/lluicolortable.h b/indra/llui/lluicolortable.h index 76518789ec..6a7a681d57 100644 --- a/indra/llui/lluicolortable.h +++ b/indra/llui/lluicolortable.h @@ -44,7 +44,7 @@ LOG_CLASS(LLUIColorTable); typedef std::map<std::string, LLUIColor> string_color_map_t; public: - struct ColorParams : LLInitParam::Choice<ColorParams> + struct ColorParams : LLInitParam::ChoiceBlock<ColorParams> { Alternative<LLColor4> value; Alternative<std::string> reference; diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 3ac3bf8c41..b9c843e931 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -36,6 +36,9 @@ static LLDefaultChildRegistry::Register<LLUICtrl> r("ui_ctrl"); +F32 LLUICtrl::sActiveControlTransparency = 1.0f; +F32 LLUICtrl::sInactiveControlTransparency = 1.0f; + // Compiler optimization, generate extern template template class LLUICtrl* LLView::getChild<class LLUICtrl>( const std::string& name, BOOL recurse) const; @@ -65,6 +68,7 @@ LLUICtrl::ControlVisibility::ControlVisibility() LLUICtrl::Params::Params() : tab_stop("tab_stop", true), chrome("chrome", false), + requests_front("requests_front", false), label("label"), initial_value("value"), init_callback("init_callback"), @@ -93,9 +97,10 @@ const LLUICtrl::Params& LLUICtrl::getDefaultParams() LLUICtrl::LLUICtrl(const LLUICtrl::Params& p, const LLViewModelPtr& viewmodel) : LLView(p), - mTentative(FALSE), mIsChrome(FALSE), + mRequestsFront(p.requests_front), mTabStop(FALSE), + mTentative(FALSE), mViewModel(viewmodel), mControlVariable(NULL), mEnabledControlVariable(NULL), @@ -110,15 +115,17 @@ LLUICtrl::LLUICtrl(const LLUICtrl::Params& p, const LLViewModelPtr& viewmodel) mMouseUpSignal(NULL), mRightMouseDownSignal(NULL), mRightMouseUpSignal(NULL), - mDoubleClickSignal(NULL) + mDoubleClickSignal(NULL), + mTransparencyType(TT_DEFAULT) { - mUICtrlHandle.bind(this); } void LLUICtrl::initFromParams(const Params& p) { LLView::initFromParams(p); + mRequestsFront = p.requests_front; + setIsChrome(p.chrome); setControlName(p.control_name); if(p.enabled_controls.isProvided()) @@ -399,6 +406,36 @@ LLViewModel* LLUICtrl::getViewModel() const return mViewModel; } +//virtual +BOOL LLUICtrl::postBuild() +{ + // + // Find all of the children that want to be in front and move them to the front + // + + if (getChildCount() > 0) + { + std::vector<LLUICtrl*> childrenToMoveToFront; + + for (LLView::child_list_const_iter_t child_it = beginChild(); child_it != endChild(); ++child_it) + { + LLUICtrl* uictrl = dynamic_cast<LLUICtrl*>(*child_it); + + if (uictrl && uictrl->mRequestsFront) + { + childrenToMoveToFront.push_back(uictrl); + } + } + + for (std::vector<LLUICtrl*>::iterator it = childrenToMoveToFront.begin(); it != childrenToMoveToFront.end(); ++it) + { + sendChildToFront(*it); + } + } + + return LLView::postBuild(); +} + bool LLUICtrl::setControlValue(const LLSD& value) { if (mControlVariable) @@ -422,7 +459,7 @@ void LLUICtrl::setControlVariable(LLControlVariable* control) if (control) { mControlVariable = control; - mControlConnection = mControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getUICtrlHandle(), std::string("value"))); + mControlConnection = mControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("value"))); setValue(mControlVariable->getValue()); } } @@ -453,7 +490,7 @@ void LLUICtrl::setEnabledControlVariable(LLControlVariable* control) if (control) { mEnabledControlVariable = control; - mEnabledControlConnection = mEnabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getUICtrlHandle(), std::string("enabled"))); + mEnabledControlConnection = mEnabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("enabled"))); setEnabled(mEnabledControlVariable->getValue().asBoolean()); } } @@ -468,7 +505,7 @@ void LLUICtrl::setDisabledControlVariable(LLControlVariable* control) if (control) { mDisabledControlVariable = control; - mDisabledControlConnection = mDisabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getUICtrlHandle(), std::string("disabled"))); + mDisabledControlConnection = mDisabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("disabled"))); setEnabled(!(mDisabledControlVariable->getValue().asBoolean())); } } @@ -483,7 +520,7 @@ void LLUICtrl::setMakeVisibleControlVariable(LLControlVariable* control) if (control) { mMakeVisibleControlVariable = control; - mMakeVisibleControlConnection = mMakeVisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getUICtrlHandle(), std::string("visible"))); + mMakeVisibleControlConnection = mMakeVisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("visible"))); setVisible(mMakeVisibleControlVariable->getValue().asBoolean()); } } @@ -498,7 +535,7 @@ void LLUICtrl::setMakeInvisibleControlVariable(LLControlVariable* control) if (control) { mMakeInvisibleControlVariable = control; - mMakeInvisibleControlConnection = mMakeInvisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getUICtrlHandle(), std::string("invisible"))); + mMakeInvisibleControlConnection = mMakeInvisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("invisible"))); setVisible(!(mMakeInvisibleControlVariable->getValue().asBoolean())); } } @@ -823,7 +860,7 @@ LLUICtrl* LLUICtrl::findRootMostFocusRoot() { LLUICtrl* focus_root = NULL; LLUICtrl* next_view = this; - while(next_view) + while(next_view && next_view->hasTabStop()) { if (next_view->isFocusRoot()) { @@ -923,6 +960,47 @@ BOOL LLUICtrl::getTentative() const void LLUICtrl::setColor(const LLColor4& color) { } +F32 LLUICtrl::getCurrentTransparency() +{ + F32 alpha = 0; + + switch(mTransparencyType) + { + case TT_DEFAULT: + alpha = getDrawContext().mAlpha; + break; + + case TT_ACTIVE: + alpha = sActiveControlTransparency; + break; + + case TT_INACTIVE: + alpha = sInactiveControlTransparency; + break; + + case TT_FADING: + alpha = sInactiveControlTransparency / 2; + break; + } + + return alpha; +} + +void LLUICtrl::setTransparencyType(ETypeTransparency type) +{ + mTransparencyType = type; +} + +boost::signals2::connection LLUICtrl::setCommitCallback(const CommitCallbackParam& cb) +{ + return setCommitCallback(initCommitCallback(cb)); +} + +boost::signals2::connection LLUICtrl::setValidateCallback(const EnableCallbackParam& cb) +{ + return setValidateCallback(initEnableCallback(cb)); +} + boost::signals2::connection LLUICtrl::setCommitCallback( const commit_signal_t::slot_type& cb ) { if (!mCommitSignal) mCommitSignal = new commit_signal_t(); @@ -976,3 +1054,9 @@ boost::signals2::connection LLUICtrl::setDoubleClickCallback( const mouse_signal if (!mDoubleClickSignal) mDoubleClickSignal = new mouse_signal_t(); return mDoubleClickSignal->connect(cb); } + +void LLUICtrl::addInfo(LLSD & info) +{ + LLView::addInfo(info); + info["value"] = getValue(); +} diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index 76dfdf754c..fb2196bb16 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -76,14 +76,14 @@ public: Optional<enable_callback_t> function; }; - struct EnableControls : public LLInitParam::Choice<EnableControls> + struct EnableControls : public LLInitParam::ChoiceBlock<EnableControls> { Alternative<std::string> enabled; Alternative<std::string> disabled; EnableControls(); }; - struct ControlVisibility : public LLInitParam::Choice<ControlVisibility> + struct ControlVisibility : public LLInitParam::ChoiceBlock<ControlVisibility> { Alternative<std::string> visible; Alternative<std::string> invisible; @@ -94,7 +94,8 @@ public: { Optional<std::string> label; Optional<bool> tab_stop, - chrome; + chrome, + requests_front; Optional<LLSD> initial_value; Optional<CommitCallbackParam> init_callback, @@ -120,6 +121,13 @@ public: Params(); }; + enum ETypeTransparency + { + TT_DEFAULT, + TT_ACTIVE, // focused floater + TT_INACTIVE, // other floaters + TT_FADING, // fading toast + }; /*virtual*/ ~LLUICtrl(); void initFromParams(const Params& p); @@ -136,6 +144,8 @@ protected: virtual LLViewModel* getViewModel() const; // We shouldn't ever need to set this directly //virtual void setViewModel(const LLViewModelPtr&); + + virtual BOOL postBuild(); public: // LLView interface @@ -202,13 +212,18 @@ public: virtual void setColor(const LLColor4& color); + F32 getCurrentTransparency(); + + void setTransparencyType(ETypeTransparency type); + ETypeTransparency getTransparencyType() const {return mTransparencyType;} + BOOL focusNextItem(BOOL text_entry_only); BOOL focusPrevItem(BOOL text_entry_only); BOOL focusFirstItem(BOOL prefer_text_fields = FALSE, BOOL focus_flash = TRUE ); BOOL focusLastItem(BOOL prefer_text_fields = FALSE); // Non Virtuals - LLHandle<LLUICtrl> getUICtrlHandle() const { return mUICtrlHandle; } + LLHandle<LLUICtrl> getHandle() const { return getDerivedHandle<LLUICtrl>(); } BOOL getIsChrome() const; void setTabStop( BOOL b ); @@ -220,6 +235,9 @@ public: // topic then put in help_topic_out bool findHelpTopic(std::string& help_topic_out); + boost::signals2::connection setCommitCallback(const CommitCallbackParam& cb); + boost::signals2::connection setValidateCallback(const EnableCallbackParam& cb); + boost::signals2::connection setCommitCallback( const commit_signal_t::slot_type& cb ); boost::signals2::connection setValidateCallback( const enable_signal_t::slot_type& cb ); @@ -283,12 +301,20 @@ protected: boost::signals2::connection mMakeVisibleControlConnection; LLControlVariable* mMakeInvisibleControlVariable; boost::signals2::connection mMakeInvisibleControlConnection; + + static F32 sActiveControlTransparency; + static F32 sInactiveControlTransparency; + + virtual void addInfo(LLSD & info); + private: - BOOL mTabStop; BOOL mIsChrome; + BOOL mRequestsFront; + BOOL mTabStop; BOOL mTentative; - LLRootHandle<LLUICtrl> mUICtrlHandle; + + ETypeTransparency mTransparencyType; class DefaultTabGroupFirstSorter; }; diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index 5de96f9d48..25e7a31e90 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -152,7 +152,27 @@ static LLFastTimer::DeclareTimer FTM_XML_PARSE("XML Reading/Parsing"); bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root) { LLFastTimer timer(FTM_XML_PARSE); - return LLXMLNode::getLayeredXMLNode(xui_filename, root, LLUI::getXUIPaths()); + + std::vector<std::string> paths; + std::string path = gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), xui_filename); + if (!path.empty()) + { + paths.push_back(path); + } + + std::string localize_path = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), xui_filename); + if (!localize_path.empty() && localize_path != path) + { + paths.push_back(localize_path); + } + + if (paths.empty()) + { + // sometimes whole path is passed in as filename + paths.push_back(xui_filename); + } + + return LLXMLNode::getLayeredXMLNode(root, paths); } @@ -214,7 +234,7 @@ LLView *LLUICtrlFactory::createFromXML(LLXMLNodePtr node, LLView* parent, const std::string LLUICtrlFactory::getCurFileName() { - return mFileNames.empty() ? "" : gDirUtilp->getWorkingDir() + gDirUtilp->getDirDelimiter() + mFileNames.back(); + return mFileNames.empty() ? "" : mFileNames.back(); } diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index 499b97f52d..d612ad5005 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -104,14 +104,17 @@ private: public: ParamDefaults() { - // recursively initialize from base class param block - ((typename PARAM_BLOCK::base_block_t&)mPrototype).fillFrom(ParamDefaults<typename PARAM_BLOCK::base_block_t, DUMMY>::instance().get()); - // after initializing base classes, look up template file for this param block + // look up template file for this param block... const std::string* param_block_tag = getWidgetTag(&typeid(PARAM_BLOCK)); if (param_block_tag) - { - LLUICtrlFactory::loadWidgetTemplate(*param_block_tag, mPrototype); + { // ...and if it exists, back fill values using the most specific template first + PARAM_BLOCK params; + LLUICtrlFactory::loadWidgetTemplate(*param_block_tag, params); + mPrototype.fillFrom(params); } + // recursively fill from base class param block + ((typename PARAM_BLOCK::base_block_t&)mPrototype).fillFrom(ParamDefaults<typename PARAM_BLOCK::base_block_t, DUMMY>::instance().get()); + } const PARAM_BLOCK& get() { return mPrototype; } @@ -169,7 +172,7 @@ public: static T* createFromFile(const std::string &filename, LLView *parent, const widget_registry_t& registry, LLXMLNodePtr output_node = NULL) { T* widget = NULL; - + std::string skinned_filename = findSkinnedFilename(filename); instance().pushFileName(filename); { @@ -198,10 +201,10 @@ public: // not of right type, so delete it if (!widget) { + llwarns << "Widget in " << filename << " was of type " << typeid(view).name() << " instead of expected type " << typeid(T).name() << llendl; delete view; view = NULL; } - } } fail: diff --git a/indra/llui/lluiimage.cpp b/indra/llui/lluiimage.cpp index 1ffad4806e..1d9ce29ba9 100644 --- a/indra/llui/lluiimage.cpp +++ b/indra/llui/lluiimage.cpp @@ -155,32 +155,32 @@ void LLUIImage::onImageLoaded() namespace LLInitParam { - void TypedParam<LLUIImage*>::setValueFromBlock() const + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateValueFromBlock() { // The keyword "none" is specifically requesting a null image // do not default to current value. Used to overwrite template images. if (name() == "none") { - mData.mValue = NULL; + updateValue(NULL); return; } LLUIImage* imagep = LLUI::getUIImage(name()); if (imagep) { - mData.mValue = imagep; + updateValue(imagep); } } - void TypedParam<LLUIImage*>::setBlockFromValue() + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateBlockFromValue(bool make_block_authoritative) { - if (mData.mValue == NULL) + if (getValue() == NULL) { - name.set("none", false); + name.set("none", make_block_authoritative); } else { - name.set(mData.mValue->getName(), false); + name.set(getValue()->getName(), make_block_authoritative); } } diff --git a/indra/llui/lluiimage.h b/indra/llui/lluiimage.h index 38107c112d..f07e8fa746 100644 --- a/indra/llui/lluiimage.h +++ b/indra/llui/lluiimage.h @@ -92,22 +92,23 @@ protected: namespace LLInitParam { template<> - class TypedParam<LLUIImage*, TypeValues<LLUIImage*>, false> - : public BlockValue<LLUIImage*> + class ParamValue<LLUIImage*, TypeValues<LLUIImage*> > + : public CustomParamValue<LLUIImage*> { typedef boost::add_reference<boost::add_const<LLUIImage*>::type>::type T_const_ref; - typedef BlockValue<LLUIImage*> super_t; + typedef CustomParamValue<LLUIImage*> super_t; public: Optional<std::string> name; - TypedParam(BlockDescriptor& descriptor, const char* name, super_t::value_assignment_t value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, name, value, func, min_count, max_count) + ParamValue(LLUIImage* const& image) + : super_t(image) { - setBlockFromValue(); + updateBlockFromValue(false); + addSynonym(name, "name"); } - void setValueFromBlock() const; - void setBlockFromValue(); + void updateValueFromBlock(); + void updateBlockFromValue(bool make_block_authoritative); }; // Need custom comparison function for our test app, which only loads diff --git a/indra/llui/lluistring.cpp b/indra/llui/lluistring.cpp index ac69d3bf85..c4e073ccdb 100644 --- a/indra/llui/lluistring.cpp +++ b/indra/llui/lluistring.cpp @@ -128,17 +128,13 @@ void LLUIString::updateResult() const } mResult = mOrig; - // get the defailt args + local args - if (!mArgs || mArgs->empty()) + // get the default args + local args + LLStringUtil::format_map_t combined_args = LLTrans::getDefaultArgs(); + if (mArgs && !mArgs->empty()) { - LLStringUtil::format(mResult, LLTrans::getDefaultArgs()); - } - else - { - LLStringUtil::format_map_t combined_args = LLTrans::getDefaultArgs(); combined_args.insert(mArgs->begin(), mArgs->end()); - LLStringUtil::format(mResult, combined_args); } + LLStringUtil::format(mResult, combined_args); } void LLUIString::updateWResult() const diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h index 4faa0e070e..cb40c85582 100644 --- a/indra/llui/lluistring.h +++ b/indra/llui/lluistring.h @@ -61,7 +61,6 @@ public: LLUIString() : mArgs(NULL), mNeedsResult(false), mNeedsWResult(false) {} LLUIString(const std::string& instring, const LLStringUtil::format_map_t& args); LLUIString(const std::string& instring) : mArgs(NULL) { assign(instring); } - ~LLUIString() { delete mArgs; } void assign(const std::string& instring); diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp index 42b779bd28..fd9b3d9a6d 100644 --- a/indra/llui/llurlaction.cpp +++ b/indra/llui/llurlaction.cpp @@ -33,28 +33,28 @@ #include "llurlregistry.h" // global state for the callback functions -void (*LLUrlAction::sOpenURLCallback) (const std::string& url) = NULL; -void (*LLUrlAction::sOpenURLInternalCallback) (const std::string& url) = NULL; -void (*LLUrlAction::sOpenURLExternalCallback) (const std::string& url) = NULL; -bool (*LLUrlAction::sExecuteSLURLCallback) (const std::string& url) = NULL; +LLUrlAction::url_callback_t LLUrlAction::sOpenURLCallback; +LLUrlAction::url_callback_t LLUrlAction::sOpenURLInternalCallback; +LLUrlAction::url_callback_t LLUrlAction::sOpenURLExternalCallback; +LLUrlAction::execute_url_callback_t LLUrlAction::sExecuteSLURLCallback; -void LLUrlAction::setOpenURLCallback(void (*cb) (const std::string& url)) +void LLUrlAction::setOpenURLCallback(url_callback_t cb) { sOpenURLCallback = cb; } -void LLUrlAction::setOpenURLInternalCallback(void (*cb) (const std::string& url)) +void LLUrlAction::setOpenURLInternalCallback(url_callback_t cb) { sOpenURLInternalCallback = cb; } -void LLUrlAction::setOpenURLExternalCallback(void (*cb) (const std::string& url)) +void LLUrlAction::setOpenURLExternalCallback(url_callback_t cb) { sOpenURLExternalCallback = cb; } -void LLUrlAction::setExecuteSLURLCallback(bool (*cb) (const std::string& url)) +void LLUrlAction::setExecuteSLURLCallback(execute_url_callback_t cb) { sExecuteSLURLCallback = cb; } @@ -63,7 +63,7 @@ void LLUrlAction::openURL(std::string url) { if (sOpenURLCallback) { - (*sOpenURLCallback)(url); + sOpenURLCallback(url); } } @@ -71,7 +71,7 @@ void LLUrlAction::openURLInternal(std::string url) { if (sOpenURLInternalCallback) { - (*sOpenURLInternalCallback)(url); + sOpenURLInternalCallback(url); } } @@ -79,7 +79,7 @@ void LLUrlAction::openURLExternal(std::string url) { if (sOpenURLExternalCallback) { - (*sOpenURLExternalCallback)(url); + sOpenURLExternalCallback(url); } } @@ -87,18 +87,18 @@ void LLUrlAction::executeSLURL(std::string url) { if (sExecuteSLURLCallback) { - (*sExecuteSLURLCallback)(url); + sExecuteSLURLCallback(url); } } void LLUrlAction::clickAction(std::string url) { // Try to handle as SLURL first, then http Url - if ( (sExecuteSLURLCallback) && !(*sExecuteSLURLCallback)(url) ) + if ( (sExecuteSLURLCallback) && !sExecuteSLURLCallback(url) ) { if (sOpenURLCallback) { - (*sOpenURLCallback)(url); + sOpenURLCallback(url); } } } diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h index 0132dbaaf0..c34960b826 100644 --- a/indra/llui/llurlaction.h +++ b/indra/llui/llurlaction.h @@ -29,6 +29,7 @@ #define LL_LLURLACTION_H #include <string> +#include <boost/function.hpp> /// /// The LLUrlAction class provides a number of static functions that @@ -77,17 +78,21 @@ public: static void showProfile(std::string url); /// specify the callbacks to enable this class's functionality - static void setOpenURLCallback(void (*cb) (const std::string& url)); - static void setOpenURLInternalCallback(void (*cb) (const std::string& url)); - static void setOpenURLExternalCallback(void (*cb) (const std::string& url)); - static void setExecuteSLURLCallback(bool (*cb) (const std::string& url)); + typedef boost::function<void (const std::string&)> url_callback_t; + typedef boost::function<bool(const std::string& url)> execute_url_callback_t; + + static void setOpenURLCallback(url_callback_t cb); + static void setOpenURLInternalCallback(url_callback_t cb); + static void setOpenURLExternalCallback(url_callback_t cb); + static void setExecuteSLURLCallback(execute_url_callback_t cb); private: // callbacks for operations we can perform on Urls - static void (*sOpenURLCallback) (const std::string& url); - static void (*sOpenURLInternalCallback) (const std::string& url); - static void (*sOpenURLExternalCallback) (const std::string& url); - static bool (*sExecuteSLURLCallback) (const std::string& url); + static url_callback_t sOpenURLCallback; + static url_callback_t sOpenURLInternalCallback; + static url_callback_t sOpenURLExternalCallback; + + static execute_url_callback_t sExecuteSLURLCallback; }; #endif diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 6cc72bad82..a9e8fbb4e4 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -27,6 +27,7 @@ #include "linden_common.h" #include "llurlentry.h" +#include "lluictrl.h" #include "lluri.h" #include "llurlmatch.h" #include "llurlregistry.h" @@ -35,6 +36,7 @@ #include "llcachename.h" #include "lltrans.h" #include "lluicolortable.h" +#include "message.h" #define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" @@ -167,6 +169,15 @@ void LLUrlEntryBase::callObservers(const std::string &id, } } +/// is this a match for a URL that should not be hyperlinked? +bool LLUrlEntryBase::isLinkDisabled() const +{ + // this allows us to have a global setting to turn off text hyperlink highlighting/action + bool globally_disabled = LLUI::sSettingGroups["config"]->getBOOL("DisableTextHyperlinkActions"); + + return globally_disabled; +} + static std::string getStringAfterToken(const std::string str, const std::string token) { size_t pos = str.find(token); @@ -456,8 +467,8 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa LLStyle::Params LLUrlEntryAgent::getStyle() const { LLStyle::Params style_params = LLUrlEntryBase::getStyle(); - style_params.color = LLUIColorTable::instance().getColor("AgentLinkColor"); - style_params.readonly_color = LLUIColorTable::instance().getColor("AgentLinkColor"); + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); return style_params; } @@ -674,8 +685,8 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa LLStyle::Params LLUrlEntryGroup::getStyle() const { LLStyle::Params style_params = LLUrlEntryBase::getStyle(); - style_params.color = LLUIColorTable::instance().getColor("GroupLinkColor"); - style_params.readonly_color = LLUIColorTable::instance().getColor("GroupLinkColor"); + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); return style_params; } @@ -730,6 +741,13 @@ std::string LLUrlEntryObjectIM::getLocation(const std::string &url) const return LLUrlEntryBase::getLocation(url); } +// LLUrlEntryParcel statics. +LLUUID LLUrlEntryParcel::sAgentID(LLUUID::null); +LLUUID LLUrlEntryParcel::sSessionID(LLUUID::null); +LLHost LLUrlEntryParcel::sRegionHost(LLHost::invalid); +bool LLUrlEntryParcel::sDisconnected(false); +std::set<LLUrlEntryParcel*> LLUrlEntryParcel::sParcelInfoObservers; + /// /// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., /// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about @@ -741,13 +759,88 @@ LLUrlEntryParcel::LLUrlEntryParcel() boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_parcel.xml"; mTooltip = LLTrans::getString("TooltipParcelUrl"); + + sParcelInfoObservers.insert(this); +} + +LLUrlEntryParcel::~LLUrlEntryParcel() +{ + sParcelInfoObservers.erase(this); } std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) { + LLSD path_array = LLURI(url).pathArray(); + S32 path_parts = path_array.size(); + + if (path_parts < 3) // no parcel id + { + llwarns << "Failed to parse url [" << url << "]" << llendl; + return url; + } + + std::string parcel_id_string = unescapeUrl(path_array[2]); // parcel id + + // Add an observer to call LLUrlLabelCallback when we have parcel name. + addObserver(parcel_id_string, url, cb); + + LLUUID parcel_id(parcel_id_string); + + sendParcelInfoRequest(parcel_id); + return unescapeUrl(url); } +void LLUrlEntryParcel::sendParcelInfoRequest(const LLUUID& parcel_id) +{ + if (sRegionHost == LLHost::invalid || sDisconnected) return; + + LLMessageSystem *msg = gMessageSystem; + msg->newMessage("ParcelInfoRequest"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, sAgentID ); + msg->addUUID("SessionID", sSessionID); + msg->nextBlock("Data"); + msg->addUUID("ParcelID", parcel_id); + msg->sendReliable(sRegionHost); +} + +void LLUrlEntryParcel::onParcelInfoReceived(const std::string &id, const std::string &label) +{ + callObservers(id, label.empty() ? LLTrans::getString("RegionInfoError") : label, mIcon); +} + +// static +void LLUrlEntryParcel::processParcelInfo(const LLParcelData& parcel_data) +{ + std::string label(LLStringUtil::null); + if (!parcel_data.name.empty()) + { + label = parcel_data.name; + } + // If parcel name is empty use Sim_name (x, y, z) for parcel label. + else if (!parcel_data.sim_name.empty()) + { + S32 region_x = llround(parcel_data.global_x) % REGION_WIDTH_UNITS; + S32 region_y = llround(parcel_data.global_y) % REGION_WIDTH_UNITS; + S32 region_z = llround(parcel_data.global_z); + + label = llformat("%s (%d, %d, %d)", + parcel_data.sim_name.c_str(), region_x, region_y, region_z); + } + + for (std::set<LLUrlEntryParcel*>::iterator iter = sParcelInfoObservers.begin(); + iter != sParcelInfoObservers.end(); + ++iter) + { + LLUrlEntryParcel* url_entry = *iter; + if (url_entry) + { + url_entry->onParcelInfoReceived(parcel_data.parcel_id.asString(), label); + } + } +} + // // LLUrlEntryPlace Describes secondlife://<location> URLs // @@ -796,6 +889,69 @@ std::string LLUrlEntryPlace::getLocation(const std::string &url) const } // +// LLUrlEntryRegion Describes secondlife:///app/region/REGION_NAME/X/Y/Z URLs, e.g. +// secondlife:///app/region/Ahern/128/128/0 +// +LLUrlEntryRegion::LLUrlEntryRegion() +{ + mPattern = boost::regex("secondlife:///app/region/[^/\\s]+(/\\d+)?(/\\d+)?(/\\d+)?/?", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_slurl.xml"; + mTooltip = LLTrans::getString("TooltipSLURL"); +} + +std::string LLUrlEntryRegion::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + // + // we handle SLURLs in the following formats: + // - secondlife:///app/region/Place/X/Y/Z + // - secondlife:///app/region/Place/X/Y + // - secondlife:///app/region/Place/X + // - secondlife:///app/region/Place + // + + LLSD path_array = LLURI(url).pathArray(); + S32 path_parts = path_array.size(); + + if (path_parts < 3) // no region name + { + llwarns << "Failed to parse url [" << url << "]" << llendl; + return url; + } + + std::string label = unescapeUrl(path_array[2]); // region name + + if (path_parts > 3) // secondlife:///app/region/Place/X + { + std::string x = path_array[3]; + label += " (" + x; + + if (path_parts > 4) // secondlife:///app/region/Place/X/Y + { + std::string y = path_array[4]; + label += "," + y; + + if (path_parts > 5) // secondlife:///app/region/Place/X/Y/Z + { + std::string z = path_array[5]; + label = label + "," + z; + } + } + + label += ")"; + } + + return label; +} + +std::string LLUrlEntryRegion::getLocation(const std::string &url) const +{ + LLSD path_array = LLURI(url).pathArray(); + std::string region_name = unescapeUrl(path_array[2]); + return region_name; +} + +// // LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., // secondlife:///app/teleport/Ahern/50/50/50/ // x-grid-location-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/ @@ -978,7 +1134,7 @@ std::string LLUrlEntryWorldMap::getLocation(const std::string &url) const // LLUrlEntryNoLink::LLUrlEntryNoLink() { - mPattern = boost::regex("<nolink>.*</nolink>", + mPattern = boost::regex("<nolink>.*?</nolink>", boost::regex::perl|boost::regex::icase); } diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 1a16056041..5f82721c0f 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -31,6 +31,9 @@ #include "lluuid.h" #include "lluicolor.h" #include "llstyle.h" + +#include "llhost.h" // for resolving parcel name by parcel id + #include <boost/signals2.hpp> #include <boost/regex.hpp> #include <string> @@ -94,6 +97,8 @@ public: virtual LLUUID getID(const std::string &string) const { return LLUUID::null; } + bool isLinkDisabled() const; + protected: std::string getIDStringFromUrl(const std::string &url) const; std::string escapeUrl(const std::string &url) const; @@ -283,8 +288,44 @@ private: class LLUrlEntryParcel : public LLUrlEntryBase { public: + struct LLParcelData + { + LLUUID parcel_id; + std::string name; + std::string sim_name; + F32 global_x; + F32 global_y; + F32 global_z; + }; + LLUrlEntryParcel(); + ~LLUrlEntryParcel(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + + // Sends a parcel info request to sim. + void sendParcelInfoRequest(const LLUUID& parcel_id); + + // Calls observers of certain parcel id providing them with parcel label. + void onParcelInfoReceived(const std::string &id, const std::string &label); + + // Processes parcel label and triggers notifying observers. + static void processParcelInfo(const LLParcelData& parcel_data); + + // Next 4 setters are used to update agent and viewer connection information + // upon events like user login, viewer disconnect and user changing region host. + // These setters are made public to be accessible from newview and should not be + // used in other cases. + static void setAgentID(const LLUUID& id) { sAgentID = id; } + static void setSessionID(const LLUUID& id) { sSessionID = id; } + static void setRegionHost(const LLHost& host) { sRegionHost = host; } + static void setDisconnected(bool disconnected) { sDisconnected = disconnected; } + +private: + static LLUUID sAgentID; + static LLUUID sSessionID; + static LLHost sRegionHost; + static bool sDisconnected; + static std::set<LLUrlEntryParcel*> sParcelInfoObservers; }; /// @@ -300,6 +341,18 @@ public: }; /// +/// LLUrlEntryRegion Describes a Second Life location Url, e.g., +/// secondlife:///app/region/Ahern/128/128/0 +/// +class LLUrlEntryRegion : public LLUrlEntryBase +{ +public: + LLUrlEntryRegion(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +}; + +/// /// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., /// secondlife:///app/teleport/Ahern/50/50/50/ /// diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 478b412d5e..523ee5d78c 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -54,6 +54,7 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntryGroup()); registerUrl(new LLUrlEntryParcel()); registerUrl(new LLUrlEntryTeleport()); + registerUrl(new LLUrlEntryRegion()); registerUrl(new LLUrlEntryWorldMap()); registerUrl(new LLUrlEntryObjectIM()); registerUrl(new LLUrlEntryPlace()); diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 3fa86bf0ca..54843227b7 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -31,7 +31,10 @@ #include "llview.h" #include <cassert> +#include <sstream> #include <boost/tokenizer.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> #include "llrender.h" #include "llevent.h" @@ -44,6 +47,7 @@ #include "v3color.h" #include "lluictrlfactory.h" #include "lltooltip.h" +#include "llsdutil.h" // for ui edit hack #include "llbutton.h" @@ -66,6 +70,8 @@ S32 LLView::sLastLeftXML = S32_MIN; S32 LLView::sLastBottomXML = S32_MIN; std::vector<LLViewDrawContext*> LLViewDrawContext::sDrawContextStack; +LLView::DrilldownFunc LLView::sDrilldown = + boost::bind(&LLView::pointInView, _1, _2, _3, HIT_TEST_USE_BOUNDING_RECT); //#if LL_DEBUG BOOL LLView::sIsDrawing = FALSE; @@ -102,6 +108,7 @@ LLView::Params::Params() left_pad("left_pad"), left_delta("left_delta", S32_MAX), from_xui("from_xui", false), + focus_root("focus_root", false), needs_translate("translate"), xmlns("xmlns"), xmlns_xsi("xmlns:xsi"), @@ -113,16 +120,17 @@ LLView::Params::Params() } LLView::LLView(const LLView::Params& p) -: mName(p.name), +: mVisible(p.visible), + mInDraw(false), + mName(p.name), mParentView(NULL), mReshapeFlags(FOLLOWS_NONE), mFromXUI(p.from_xui), - mIsFocusRoot(FALSE), + mIsFocusRoot(p.focus_root), mLastVisible(FALSE), mNextInsertionOrdinal(0), mHoverCursor(getCursorFromString(p.hover_cursor)), mEnabled(p.enabled), - mVisible(p.visible), mMouseOpaque(p.mouse_opaque), mSoundFlags(p.sound_flags), mUseBoundingRect(p.use_bounding_rect), @@ -163,8 +171,6 @@ LLView::~LLView() if (mDefaultWidgets) { - std::for_each(mDefaultWidgets->begin(), mDefaultWidgets->end(), - DeletePairedPointer()); delete mDefaultWidgets; mDefaultWidgets = NULL; } @@ -220,9 +226,11 @@ BOOL LLView::getUseBoundingRect() const } // virtual -std::string LLView::getName() const +const std::string& LLView::getName() const { - return mName.empty() ? std::string("(no name)") : mName; + static std::string no_name("(no name)"); + + return mName.empty() ? no_name : mName; } void LLView::sendChildToFront(LLView* child) @@ -326,6 +334,8 @@ void LLView::removeChild(LLView* child) //llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs if (child->mParentView == this) { + // if we are removing an item we are currently iterating over, that would be bad + llassert(child->mInDraw == false); mChildList.remove( child ); child->mParentView = NULL; if (child->isCtrl()) @@ -339,7 +349,7 @@ void LLView::removeChild(LLView* child) } else { - llerrs << "LLView::removeChild called with non-child" << llendl; + llwarns << child->getName() << "is not a child of " << getName() << llendl; } updateBoundingRect(); } @@ -347,13 +357,11 @@ void LLView::removeChild(LLView* child) LLView::ctrl_list_t LLView::getCtrlList() const { ctrl_list_t controls; - for(child_list_const_iter_t iter = mChildList.begin(); - iter != mChildList.end(); - iter++) + BOOST_FOREACH(LLView* viewp, mChildList) { - if((*iter)->isCtrl()) + if(viewp->isCtrl()) { - controls.push_back(static_cast<LLUICtrl*>(*iter)); + controls.push_back(static_cast<LLUICtrl*>(viewp)); } } return controls; @@ -429,6 +437,36 @@ BOOL LLView::isInEnabledChain() const return enabled; } +static void buildPathname(std::ostream& out, const LLView* view) +{ + if (! (view && view->getParent())) + { + return; // Don't include root in the path. + } + + buildPathname(out, view->getParent()); + + // Build pathname into ostream on the way back from recursion. + out << '/' << view->getName(); +} + +std::string LLView::getPathname() const +{ + std::ostringstream out; + buildPathname(out, this); + return out.str(); +} + +//static +std::string LLView::getPathname(const LLView* view) +{ + if (! view) + { + return "NULL"; + } + return view->getPathname(); +} + // virtual BOOL LLView::canFocusChildren() const { @@ -575,9 +613,8 @@ void LLView::deleteAllChildren() void LLView::setAllChildrenEnabled(BOOL b) { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + BOOST_FOREACH(LLView* viewp, mChildList) { - LLView* viewp = *child_it; viewp->setEnabled(b); } } @@ -603,9 +640,8 @@ void LLView::setVisible(BOOL visible) // virtual void LLView::handleVisibilityChange ( BOOL new_visibility ) { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + BOOST_FOREACH(LLView* viewp, mChildList) { - LLView* viewp = *child_it; // only views that are themselves visible will have their overall visibility affected by their ancestors if (viewp->getVisible()) { @@ -647,56 +683,178 @@ void LLView::onMouseLeave(S32 x, S32 y, MASK mask) //llinfos << "Mouse left " << getName() << llendl; } +bool LLView::visibleAndContains(S32 local_x, S32 local_y) +{ + return sDrilldown(this, local_x, local_y) + && getVisible(); +} + +bool LLView::visibleEnabledAndContains(S32 local_x, S32 local_y) +{ + return visibleAndContains(local_x, local_y) + && getEnabled(); +} + +void LLView::logMouseEvent() +{ + if (sDebugMouseHandling) + { + sMouseHandlerMessage = std::string("/") + mName + sMouseHandlerMessage; + } +} + +template <typename METHOD, typename CHARTYPE> +LLView* LLView::childrenHandleCharEvent(const std::string& desc, const METHOD& method, + CHARTYPE c, MASK mask) +{ + if ( getVisible() && getEnabled() ) + { + BOOST_FOREACH(LLView* viewp, mChildList) + { + if ((viewp->*method)(c, mask, TRUE)) + { + if (LLView::sDebugKeys) + { + llinfos << desc << " handled by " << viewp->getName() << llendl; + } + return viewp; + } + } + } + return NULL; +} + +// XDATA might be MASK, or S32 clicks +template <typename METHOD, typename XDATA> +LLView* LLView::childrenHandleMouseEvent(const METHOD& method, S32 x, S32 y, XDATA extra, bool allow_mouse_block) +{ + BOOST_FOREACH(LLView* viewp, mChildList) + { + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + + if (!viewp->visibleEnabledAndContains(local_x, local_y)) + { + continue; + } + + if ((viewp->*method)( local_x, local_y, extra ) + || (allow_mouse_block && viewp->blockMouseEvent( local_x, local_y ))) + { + viewp->logMouseEvent(); + return viewp; + } + } + return NULL; +} LLView* LLView::childrenHandleToolTip(S32 x, S32 y, MASK mask) { - LLView* handled_view = NULL; - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + BOOST_FOREACH(LLView* viewp, mChildList) { - LLView* viewp = *child_it; S32 local_x = x - viewp->getRect().mLeft; S32 local_y = y - viewp->getRect().mBottom; - if(!viewp->pointInView(local_x, local_y) - || !viewp->getVisible()) + // Differs from childrenHandleMouseEvent() in that we want to offer + // tooltips even for disabled widgets. + if(!viewp->visibleAndContains(local_x, local_y)) { continue; } - if (viewp->handleToolTip(local_x, local_y, mask) ) + if (viewp->handleToolTip(local_x, local_y, mask) + || viewp->blockMouseEvent(local_x, local_y)) { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } + viewp->logMouseEvent(); + return viewp; + } + } + return NULL; +} - handled_view = viewp; - break; +LLView* LLView::childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // default to not accepting drag and drop, will be overridden by handler + *accept = ACCEPT_NO; + + BOOST_FOREACH(LLView* viewp, mChildList) + { + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if( !viewp->visibleEnabledAndContains(local_x, local_y)) + { + continue; } - if (viewp->blockMouseEvent(local_x, local_y)) + // Differs from childrenHandleMouseEvent() simply in that this virtual + // method call diverges pretty radically from the usual (x, y, int). + if (viewp->handleDragAndDrop(local_x, local_y, mask, drop, + cargo_type, + cargo_data, + accept, + tooltip_msg) + || viewp->blockMouseEvent(local_x, local_y)) { - handled_view = viewp; - break; + return viewp; } } - return handled_view; + return NULL; } +LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) +{ + BOOST_FOREACH(LLView* viewp, mChildList) + { + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if(!viewp->visibleEnabledAndContains(local_x, local_y)) + { + continue; + } + + // This call differentiates this method from childrenHandleMouseEvent(). + LLUI::sWindow->setCursor(viewp->getHoverCursor()); + + if (viewp->handleHover(local_x, local_y, mask) + || viewp->blockMouseEvent(local_x, local_y)) + { + viewp->logMouseEvent(); + return viewp; + } + } + return NULL; +} -LLView* LLView::childFromPoint(S32 x, S32 y) +LLView* LLView::childFromPoint(S32 x, S32 y, bool recur) { - if (!getVisible() ) + if (!getVisible()) return false; - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + + BOOST_FOREACH(LLView* viewp, mChildList) { - LLView* viewp = *child_it; S32 local_x = x - viewp->getRect().mLeft; S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() ) + if (!viewp->visibleAndContains(local_x, local_y)) { continue; } + // Here we've found the first (frontmost) visible child at this level + // containing the specified point. Is the caller asking us to drill + // down and return the innermost leaf child at this point, or just the + // top-level child? + if (recur) + { + LLView* leaf(viewp->childFromPoint(local_x, local_y, recur)); + // Maybe viewp is already a leaf LLView, or maybe it has children + // but this particular (x, y) point falls between them. If the + // recursive call returns non-NULL, great, use that; else just use + // viewp. + return leaf? leaf : viewp; + } return viewp; } @@ -709,15 +867,16 @@ BOOL LLView::handleToolTip(S32 x, S32 y, MASK mask) // parents provide tooltips first, which are optionally // overridden by children, in case child is mouse_opaque - if (!mToolTipMsg.empty()) + std::string tooltip = getToolTip(); + if (!tooltip.empty()) { // allow "scrubbing" over ui by showing next tooltip immediately // if previous one was still visible F32 timeout = LLToolTipMgr::instance().toolTipVisible() - ? 0.f + ? LLUI::sSettingGroups["config"]->getF32( "ToolTipFastDelay" ) : LLUI::sSettingGroups["config"]->getF32( "ToolTipDelay" ); LLToolTipMgr::instance().show(LLToolTip::Params() - .message(mToolTipMsg) + .message(tooltip) .sticky_rect(calcScreenRect()) .delay_time(timeout)); @@ -816,45 +975,6 @@ BOOL LLView::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, return childrenHandleDragAndDrop( x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL; } -LLView* LLView::childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, - BOOL drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - LLView* handled_view = NULL; - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if( !viewp->pointInView(local_x, local_y) || - !viewp->getVisible() || - !viewp->getEnabled()) - { - continue; - } - if (viewp->handleDragAndDrop(local_x, local_y, mask, drop, - cargo_type, - cargo_data, - accept, - tooltip_msg)) - { - handled_view = viewp; - break; - } - - if (viewp->blockMouseEvent(x, y)) - { - *accept = ACCEPT_NO; - handled_view = viewp; - break; - } - } - return handled_view; -} - void LLView::onMouseCaptureLost() { } @@ -904,391 +1024,57 @@ BOOL LLView::handleMiddleMouseUp(S32 x, S32 y, MASK mask) return childrenHandleMiddleMouseUp( x, y, mask ) != NULL; } - LLView* LLView::childrenHandleScrollWheel(S32 x, S32 y, S32 clicks) { - LLView* handled_view = NULL; - if (getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled()) - { - continue; - } - - if (viewp->handleScrollWheel( local_x, local_y, clicks )) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - - handled_view = viewp; - break; - } - } - } - return handled_view; -} - -LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) -{ - LLView* handled_view = NULL; - if (getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if(!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled()) - { - continue; - } - - if (viewp->handleHover(local_x, local_y, mask) ) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - - handled_view = viewp; - break; - } - - if (viewp->blockMouseEvent(local_x, local_y)) - { - LLUI::sWindow->setCursor(viewp->getHoverCursor()); - - handled_view = viewp; - break; - } - } - } - return handled_view; + return childrenHandleMouseEvent(&LLView::handleScrollWheel, x, y, clicks, false); } // Called during downward traversal LLView* LLView::childrenHandleKey(KEY key, MASK mask) { - LLView* handled_view = NULL; - - if ( getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - if (viewp->handleKey(key, mask, TRUE)) - { - if (LLView::sDebugKeys) - { - llinfos << "Key handled by " << viewp->getName() << llendl; - } - handled_view = viewp; - break; - } - } - } - - return handled_view; + return childrenHandleCharEvent("Key", &LLView::handleKey, key, mask); } // Called during downward traversal LLView* LLView::childrenHandleUnicodeChar(llwchar uni_char) { - LLView* handled_view = NULL; - - if ( getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - if (viewp->handleUnicodeChar(uni_char, TRUE)) - { - if (LLView::sDebugKeys) - { - llinfos << "Unicode character handled by " << viewp->getName() << llendl; - } - handled_view = viewp; - break; - } - } - } - - return handled_view; + return childrenHandleCharEvent("Unicode character", &LLView::handleUnicodeCharWithDummyMask, + uni_char, MASK_NONE); } LLView* LLView::childrenHandleMouseDown(S32 x, S32 y, MASK mask) { - LLView* handled_view = NULL; - - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled()) - { - continue; - } - - if(viewp->handleMouseDown( local_x, local_y, mask )) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - handled_view = viewp; - break; - } - - if(viewp->blockMouseEvent(local_x, local_y)) - { - handled_view = viewp; - break; - } - } - return handled_view; + return childrenHandleMouseEvent(&LLView::handleMouseDown, x, y, mask); } LLView* LLView::childrenHandleRightMouseDown(S32 x, S32 y, MASK mask) { - LLView* handled_view = NULL; - - if (getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled()) - { - continue; - } - - if (viewp->handleRightMouseDown( local_x, local_y, mask )) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - - handled_view = viewp; - break; - } - - if (viewp->blockMouseEvent(local_x, local_y)) - { - handled_view = viewp; - break; - } - } - } - return handled_view; + return childrenHandleMouseEvent(&LLView::handleRightMouseDown, x, y, mask); } LLView* LLView::childrenHandleMiddleMouseDown(S32 x, S32 y, MASK mask) { - LLView* handled_view = NULL; - - if (getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled()) - { - continue; - } - - if(viewp->handleMiddleMouseDown( local_x, local_y, mask )) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - handled_view = viewp; - break; - } - - if (viewp->blockMouseEvent(local_x, local_y)) - { - handled_view = viewp; - break; - } - } - } - return handled_view; + return childrenHandleMouseEvent(&LLView::handleMiddleMouseDown, x, y, mask); } LLView* LLView::childrenHandleDoubleClick(S32 x, S32 y, MASK mask) { - LLView* handled_view = NULL; - - if (getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled()) - { - continue; - } - - if (viewp->handleDoubleClick( local_x, local_y, mask )) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - handled_view = viewp; - break; - } - - if (viewp->blockMouseEvent(local_x, local_y)) - { - handled_view = viewp; - break; - } - } - } - return handled_view; + return childrenHandleMouseEvent(&LLView::handleDoubleClick, x, y, mask); } LLView* LLView::childrenHandleMouseUp(S32 x, S32 y, MASK mask) { - LLView* handled_view = NULL; - if( getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled()) - { - continue; - } - - if (viewp->handleMouseUp( local_x, local_y, mask )) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - handled_view = viewp; - break; - } - - if (viewp->blockMouseEvent(local_x, local_y)) - { - handled_view = viewp; - break; - } - } - } - return handled_view; + return childrenHandleMouseEvent(&LLView::handleMouseUp, x, y, mask); } LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) { - LLView* handled_view = NULL; - if( getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled() ) - { - continue; - } - - if(viewp->handleRightMouseUp( local_x, local_y, mask )) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - handled_view = viewp; - break; - } - - if(viewp->blockMouseEvent(local_x, local_y)) - { - handled_view = viewp; - break; - } - } - } - return handled_view; + return childrenHandleMouseEvent(&LLView::handleRightMouseUp, x, y, mask); } LLView* LLView::childrenHandleMiddleMouseUp(S32 x, S32 y, MASK mask) { - LLView* handled_view = NULL; - if( getVisible() && getEnabled() ) - { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->pointInView(local_x, local_y) - || !viewp->getVisible() - || !viewp->getEnabled()) - { - continue; - } - - if(viewp->handleMiddleMouseUp( local_x, local_y, mask )) - { - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; - } - handled_view = viewp; - break; - } - - if (viewp->blockMouseEvent(local_x, local_y)) - { - handled_view = viewp; - break; - } - } - } - return handled_view; + return childrenHandleMouseEvent(&LLView::handleMiddleMouseUp, x, y, mask); } void LLView::draw() @@ -1300,26 +1086,31 @@ void LLView::drawChildren() { if (!mChildList.empty()) { - LLRect rootRect = getRootView()->getRect(); - LLRect screenRect; - + LLView* rootp = LLUI::getRootView(); ++sDepth; for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend();) // ++child_iter) { child_list_reverse_iter_t child = child_iter++; LLView *viewp = *child; + + if (viewp == NULL) + { + continue; + } if (viewp->getVisible() && viewp->getRect().isValid()) { - // Only draw views that are within the root view - localRectToScreen(viewp->getRect(),&screenRect); - if ( rootRect.overlaps(screenRect) && LLUI::sDirtyRect.overlaps(screenRect)) + LLRect screen_rect = viewp->calcScreenRect(); + if ( rootp->getLocalRect().overlaps(screen_rect) && LLUI::sDirtyRect.overlaps(screen_rect)) { LLUI::pushMatrix(); { - LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom, 0.f); + LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom); + // flag the fact we are in draw here, in case overridden draw() method attempts to remove this widget + viewp->mInDraw = true; viewp->draw(); + viewp->mInDraw = false; if (sDebugRects) { @@ -1368,7 +1159,7 @@ void LLView::drawDebugRect() if (getUseBoundingRect()) { - LLUI::translate((F32)mBoundingRect.mLeft - (F32)mRect.mLeft, (F32)mBoundingRect.mBottom - (F32)mRect.mBottom, 0.f); + LLUI::translate((F32)mBoundingRect.mLeft - (F32)mRect.mLeft, (F32)mBoundingRect.mBottom - (F32)mRect.mBottom); } LLRect debug_rect = getUseBoundingRect() ? mBoundingRect : mRect; @@ -1437,10 +1228,10 @@ void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset, BOOL force_dr if ((childp->getVisible() && childp->getRect().isValid()) || force_draw) { - glMatrixMode(GL_MODELVIEW); + gGL.matrixMode(LLRender::MM_MODELVIEW); LLUI::pushMatrix(); { - LLUI::translate((F32)childp->getRect().mLeft + x_offset, (F32)childp->getRect().mBottom + y_offset, 0.f); + LLUI::translate((F32)childp->getRect().mLeft + x_offset, (F32)childp->getRect().mBottom + y_offset); childp->draw(); } LLUI::popMatrix(); @@ -1464,9 +1255,8 @@ void LLView::reshape(S32 width, S32 height, BOOL called_from_parent) mRect.mTop = getRect().mBottom + height; // move child views according to reshape flags - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + BOOST_FOREACH(LLView* viewp, mChildList) { - LLView* viewp = *child_it; LLRect child_rect( viewp->mRect ); if (viewp->followsRight() && viewp->followsLeft()) @@ -1510,7 +1300,10 @@ void LLView::reshape(S32 width, S32 height, BOOL called_from_parent) S32 delta_x = child_rect.mLeft - viewp->getRect().mLeft; S32 delta_y = child_rect.mBottom - viewp->getRect().mBottom; viewp->translate( delta_x, delta_y ); - viewp->reshape(child_rect.getWidth(), child_rect.getHeight()); + if (child_rect.getWidth() != viewp->getRect().getWidth() || child_rect.getHeight() != viewp->getRect().getHeight()) + { + viewp->reshape(child_rect.getWidth(), child_rect.getHeight()); + } } } @@ -1529,10 +1322,8 @@ LLRect LLView::calcBoundingRect() { LLRect local_bounding_rect = LLRect::null; - child_list_const_iter_t child_it; - for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + BOOST_FOREACH(LLView* childp, mChildList) { - LLView* childp = *child_it; // ignore invisible and "top" children when calculating bounding rect // such as combobox popups if (!childp->getVisible() || childp == gFocusMgr.getTopCtrl()) @@ -1659,15 +1450,19 @@ BOOL LLView::hasAncestor(const LLView* parentp) const BOOL LLView::childHasKeyboardFocus( const std::string& childname ) const { - LLView *child = findChildView(childname, TRUE); - if (child) - { - return gFocusMgr.childHasKeyboardFocus(child); - } - else + LLView *focus = dynamic_cast<LLView *>(gFocusMgr.getKeyboardFocus()); + + while (focus != NULL) { - return FALSE; + if (focus->getName() == childname) + { + return TRUE; + } + + focus = focus->getParent(); } + + return FALSE; } //----------------------------------------------------------------------------- @@ -1682,18 +1477,7 @@ BOOL LLView::hasChild(const std::string& childname, BOOL recurse) const //----------------------------------------------------------------------------- LLView* LLView::getChildView(const std::string& name, BOOL recurse) const { - LLView* child = findChildView(name, recurse); - if (!child) - { - child = getDefaultWidget<LLView>(name); - if (!child) - { - LLView::Params view_params; - view_params.name = name; - child = LLUICtrlFactory::create<LLView>(view_params); - } - } - return child; + return getChild<LLView>(name, recurse); } static LLFastTimer::DeclareTimer FTM_FIND_VIEWS("Find Widgets"); @@ -1704,11 +1488,9 @@ LLView* LLView::findChildView(const std::string& name, BOOL recurse) const //richard: should we allow empty names? //if(name.empty()) // return NULL; - child_list_const_iter_t child_it; // Look for direct children *first* - for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + BOOST_FOREACH(LLView* childp, mChildList) { - LLView* childp = *child_it; llassert(childp); if (childp->getName() == name) { @@ -1718,9 +1500,8 @@ LLView* LLView::findChildView(const std::string& name, BOOL recurse) const if (recurse) { // Look inside each child as well. - for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + BOOST_FOREACH(LLView* childp, mChildList) { - LLView* childp = *child_it; llassert(childp); LLView* viewp = childp->findChildView(name, recurse); if ( viewp ) @@ -1837,67 +1618,31 @@ LLView* LLView::findNextSibling(LLView* child) return (next_it != mChildList.end()) ? *next_it : NULL; } -void LLView::deleteViewByHandle(LLHandle<LLView> handle) -{ - LLView* viewp = handle.get(); - - delete viewp; -} - -LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, BOOL allow_partial_outside) +LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, S32 min_overlap_pixels) { LLCoordGL delta; - if (allow_partial_outside) - { - const S32 KEEP_ONSCREEN_PIXELS = 16; + const S32 KEEP_ONSCREEN_PIXELS_WIDTH = llmin(min_overlap_pixels, input.getWidth()); + const S32 KEEP_ONSCREEN_PIXELS_HEIGHT = llmin(min_overlap_pixels, input.getHeight()); - if( input.mRight - KEEP_ONSCREEN_PIXELS < constraint.mLeft ) - { - delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS); - } - else - if( input.mLeft + KEEP_ONSCREEN_PIXELS > constraint.mRight ) - { - delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS); - } + if( input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH < constraint.mLeft ) + { + delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH); + } + else if( input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH > constraint.mRight ) + { + delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH); + } - if( input.mTop > constraint.mTop ) - { - delta.mY = constraint.mTop - input.mTop; - } - else - if( input.mTop - KEEP_ONSCREEN_PIXELS < constraint.mBottom ) - { - delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS); - } + if( input.mTop > constraint.mTop ) + { + delta.mY = constraint.mTop - input.mTop; } else + if( input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT < constraint.mBottom ) { - if( input.mLeft < constraint.mLeft ) - { - delta.mX = constraint.mLeft - input.mLeft; - } - else - if( input.mRight > constraint.mRight ) - { - delta.mX = constraint.mRight - input.mRight; - // compensate for left edge possible going off screen - delta.mX += llmax( 0, input.getWidth() - constraint.getWidth() ); - } - - if( input.mTop > constraint.mTop ) - { - delta.mY = constraint.mTop - input.mTop; - } - else - if( input.mBottom < constraint.mBottom ) - { - delta.mY = constraint.mBottom - input.mBottom; - // compensate for top edge possible going off screen - delta.mY -= llmax( 0, input.getHeight() - constraint.getHeight() ); - } + delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT); } return delta; @@ -1906,9 +1651,9 @@ LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, BO // Moves the view so that it is entirely inside of constraint. // If the view will not fit because it's too big, aligns with the top and left. // (Why top and left? That's where the drag bars are for floaters.) -BOOL LLView::translateIntoRect(const LLRect& constraint, BOOL allow_partial_outside ) +BOOL LLView::translateIntoRect(const LLRect& constraint, S32 min_overlap_pixels) { - LLCoordGL translation = getNeededTranslation(getRect(), constraint, allow_partial_outside); + LLCoordGL translation = getNeededTranslation(getRect(), constraint, min_overlap_pixels); if (translation.mX != 0 || translation.mY != 0) { @@ -1920,9 +1665,9 @@ BOOL LLView::translateIntoRect(const LLRect& constraint, BOOL allow_partial_outs // move this view into "inside" but not onto "exclude" // NOTE: if this view is already contained in "inside", we ignore the "exclude" rect -BOOL LLView::translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, BOOL allow_partial_outside ) +BOOL LLView::translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, S32 min_overlap_pixels) { - LLCoordGL translation = getNeededTranslation(getRect(), inside, allow_partial_outside); + LLCoordGL translation = getNeededTranslation(getRect(), inside, min_overlap_pixels); if (translation.mX != 0 || translation.mY != 0) { @@ -1970,7 +1715,7 @@ void LLView::centerWithin(const LLRect& bounds) translate( left - getRect().mLeft, bottom - getRect().mBottom ); } -BOOL LLView::localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, LLView* other_view) const +BOOL LLView::localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, const LLView* other_view) const { const LLView* cur_view = this; const LLView* root_view = NULL; @@ -2013,7 +1758,7 @@ BOOL LLView::localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, LL return FALSE; } -BOOL LLView::localRectToOtherView( const LLRect& local, LLRect* other, LLView* other_view ) const +BOOL LLView::localRectToOtherView( const LLRect& local, LLRect* other, const LLView* other_view ) const { LLRect cur_rect = local; const LLView* cur_view = this; @@ -2090,7 +1835,10 @@ const LLCtrlQuery & LLView::getFocusRootsQuery() void LLView::setShape(const LLRect& new_rect, bool by_user) { - handleReshape(new_rect, by_user); + if (new_rect != getRect()) + { + handleReshape(new_rect, by_user); + } } void LLView::handleReshape(const LLRect& new_rect, bool by_user) @@ -2480,110 +2228,163 @@ static bool get_last_child_rect(LLView* parent, LLRect *rect) } //static -void LLView::applyXUILayout(LLView::Params& p, LLView* parent) +void LLView::applyXUILayout(LLView::Params& p, LLView* parent, LLRect layout_rect) { + if (!parent) return; + const S32 VPAD = 4; const S32 MIN_WIDGET_HEIGHT = 10; // *NOTE: This will confuse export of floater/panel coordinates unless // the default is also "topleft". JC - if (p.layout().empty() && parent) + if (p.layout().empty()) { p.layout = parent->getLayout(); } - if (parent) + if (layout_rect.isEmpty()) { - LLRect parent_rect = parent->getLocalRect(); - // overwrite uninitialized rect params, using context - LLRect last_rect = parent->getLocalRect(); + layout_rect = parent->getLocalRect(); + } - bool layout_topleft = (p.layout() == "topleft"); + // overwrite uninitialized rect params, using context + LLRect default_rect = parent->getLocalRect(); - // convert negative or centered coordinates to parent relative values - // Note: some of this logic matches the logic in TypedParam<LLRect>::setValueFromBlock() - if (p.rect.left.isProvided() && p.rect.left < 0) p.rect.left = p.rect.left + parent_rect.getWidth(); - if (p.rect.right.isProvided() && p.rect.right < 0) p.rect.right = p.rect.right + parent_rect.getWidth(); - if (p.rect.bottom.isProvided() && p.rect.bottom < 0) p.rect.bottom = p.rect.bottom + parent_rect.getHeight(); - if (p.rect.top.isProvided() && p.rect.top < 0) p.rect.top = p.rect.top + parent_rect.getHeight(); + bool layout_topleft = (p.layout() == "topleft"); + // convert negative or centered coordinates to parent relative values + // Note: some of this logic matches the logic in TypedParam<LLRect>::setValueFromBlock() + if (p.rect.left.isProvided()) + { + p.rect.left = p.rect.left + ((p.rect.left >= 0) ? layout_rect.mLeft : layout_rect.mRight); + } + if (p.rect.right.isProvided()) + { + p.rect.right = p.rect.right + ((p.rect.right >= 0) ? layout_rect.mLeft : layout_rect.mRight); + } + if (p.rect.bottom.isProvided()) + { + p.rect.bottom = p.rect.bottom + ((p.rect.bottom >= 0) ? layout_rect.mBottom : layout_rect.mTop); if (layout_topleft) { //invert top to bottom - if (p.rect.top.isProvided()) p.rect.top = parent_rect.getHeight() - p.rect.top; - if (p.rect.bottom.isProvided()) p.rect.bottom = parent_rect.getHeight() - p.rect.bottom; + p.rect.bottom = layout_rect.mBottom + layout_rect.mTop - p.rect.bottom; } - - // DEPRECATE: automatically fall back to height of MIN_WIDGET_HEIGHT pixels - if (!p.rect.height.isProvided() && !p.rect.top.isProvided() && p.rect.height == 0) + } + if (p.rect.top.isProvided()) + { + p.rect.top = p.rect.top + ((p.rect.top >= 0) ? layout_rect.mBottom : layout_rect.mTop); + if (layout_topleft) { - p.rect.height = MIN_WIDGET_HEIGHT; + //invert top to bottom + p.rect.top = layout_rect.mBottom + layout_rect.mTop - p.rect.top; } + } + + // DEPRECATE: automatically fall back to height of MIN_WIDGET_HEIGHT pixels + if (!p.rect.height.isProvided() && !p.rect.top.isProvided() && p.rect.height == 0) + { + p.rect.height = MIN_WIDGET_HEIGHT; + } - last_rect.translate(0, last_rect.getHeight()); + default_rect.translate(0, default_rect.getHeight()); - // If there was a recently constructed child, use its rectangle - get_last_child_rect(parent, &last_rect); + // If there was a recently constructed child, use its rectangle + get_last_child_rect(parent, &default_rect); - if (layout_topleft) + if (layout_topleft) + { + // Invert the sense of bottom_delta for topleft layout + if (p.bottom_delta.isProvided()) { - p.bottom_delta.setIfNotProvided(0, false); - - // Invert the sense of bottom_delta for topleft layout - if (p.bottom_delta.isProvided()) - { - p.bottom_delta = -p.bottom_delta; - } - else if (p.top_pad.isProvided()) - { - p.bottom_delta = -(p.rect.height + p.top_pad); - } - else if (p.top_delta.isProvided()) - { - p.bottom_delta = - -(p.top_delta + p.rect.height - last_rect.getHeight()); - } - else if (!p.bottom_delta.isProvided() - && !p.left_delta.isProvided() - && !p.top_pad.isProvided() - && !p.left_pad.isProvided()) - { - // set default position is just below last rect - p.bottom_delta.set(-(p.rect.height + VPAD), false); - } + p.bottom_delta = -p.bottom_delta; + } + else if (p.top_pad.isProvided()) + { + p.bottom_delta = -(p.rect.height + p.top_pad); + } + else if (p.top_delta.isProvided()) + { + p.bottom_delta = + -(p.top_delta + p.rect.height - default_rect.getHeight()); + } + else if (!p.left_delta.isProvided() + && !p.left_pad.isProvided()) + { + // set default position is just below last rect + p.bottom_delta.set(-(p.rect.height + VPAD), false); + } + else + { + p.bottom_delta.set(0, false); + } - // default to same left edge - p.left_delta.setIfNotProvided(0, false); - if (p.left_pad.isProvided()) - { - // left_pad is based on prior widget's right edge - p.left_delta.set(p.left_pad + last_rect.getWidth(), false); - } + // default to same left edge + if (!p.left_delta.isProvided()) + { + p.left_delta.set(0, false); + } + if (p.left_pad.isProvided()) + { + // left_pad is based on prior widget's right edge + p.left_delta.set(p.left_pad + default_rect.getWidth(), false); + } - last_rect.translate(p.left_delta, p.bottom_delta); + default_rect.translate(p.left_delta, p.bottom_delta); + } + else + { + // set default position is just below last rect + if (!p.bottom_delta.isProvided()) + { + p.bottom_delta.set(-(p.rect.height + VPAD), false); } - else - { - // set default position is just below last rect - p.bottom_delta.setIfNotProvided(-(p.rect.height + VPAD), false); - p.left_delta.setIfNotProvided(0, false); - last_rect.translate(p.left_delta, p.bottom_delta); + if (!p.left_delta.isProvided()) + { + p.left_delta.set(0, false); } + default_rect.translate(p.left_delta, p.bottom_delta); + } - // this handles case where *both* x and x_delta are provided - // ignore x in favor of default x + x_delta - if (p.bottom_delta.isProvided()) p.rect.bottom.set(0, false); - if (p.left_delta.isProvided()) p.rect.left.set(0, false); + // this handles case where *both* x and x_delta are provided + // ignore x in favor of default x + x_delta + if (p.bottom_delta.isProvided()) p.rect.bottom.set(0, false); + if (p.left_delta.isProvided()) p.rect.left.set(0, false); - // selectively apply rectangle defaults, making sure that - // params are not flagged as having been "provided" - // as rect params are overconstrained and rely on provided flags - p.rect.left.setIfNotProvided(last_rect.mLeft, false); - p.rect.bottom.setIfNotProvided(last_rect.mBottom, false); - p.rect.top.setIfNotProvided(last_rect.mTop, false); - p.rect.right.setIfNotProvided(last_rect.mRight, false); - p.rect.width.setIfNotProvided(last_rect.getWidth(), false); - p.rect.height.setIfNotProvided(last_rect.getHeight(), false); + // selectively apply rectangle defaults, making sure that + // params are not flagged as having been "provided" + // as rect params are overconstrained and rely on provided flags + if (!p.rect.left.isProvided()) + { + p.rect.left.set(default_rect.mLeft, false); + //HACK: get around the fact that setting a rect param component value won't invalidate the existing rect object value + p.rect.paramChanged(p.rect.left, true); + } + if (!p.rect.bottom.isProvided()) + { + p.rect.bottom.set(default_rect.mBottom, false); + p.rect.paramChanged(p.rect.bottom, true); + } + if (!p.rect.top.isProvided()) + { + p.rect.top.set(default_rect.mTop, false); + p.rect.paramChanged(p.rect.top, true); + } + if (!p.rect.right.isProvided()) + { + p.rect.right.set(default_rect.mRight, false); + p.rect.paramChanged(p.rect.right, true); + + } + if (!p.rect.width.isProvided()) + { + p.rect.width.set(default_rect.getWidth(), false); + p.rect.paramChanged(p.rect.width, true); + } + if (!p.rect.height.isProvided()) + { + p.rect.height.set(default_rect.getHeight(), false); + p.rect.paramChanged(p.rect.height, true); } } @@ -2804,11 +2605,14 @@ LLView::root_to_view_iterator_t LLView::endRootToView() // only create maps on demand, as they incur heap allocation/deallocation cost // when a view is constructed/deconstructed -LLView::default_widget_map_t& LLView::getDefaultWidgetMap() const +LLView& LLView::getDefaultWidgetContainer() const { if (!mDefaultWidgets) { - mDefaultWidgets = new default_widget_map_t(); + LLView::Params p; + p.name = "default widget container"; + p.visible = false; // ensures default widgets can't steal focus, etc. + mDefaultWidgets = new LLView(p); } return *mDefaultWidgets; } @@ -2823,9 +2627,9 @@ S32 LLView::notifyParent(const LLSD& info) bool LLView::notifyChildren(const LLSD& info) { bool ret = false; - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + BOOST_FOREACH(LLView* childp, mChildList) { - ret |= (*child_it)->notifyChildren(info); + ret = ret || childp->notifyChildren(info); } return ret; } @@ -2845,3 +2649,24 @@ const LLViewDrawContext& LLViewDrawContext::getCurrentContext() return *sDrawContextStack.back(); } + +LLSD LLView::getInfo(void) +{ + LLSD info; + addInfo(info); + return info; +} + +void LLView::addInfo(LLSD & info) +{ + info["path"] = getPathname(); + info["class"] = typeid(*this).name(); + info["visible"] = getVisible(); + info["visible_chain"] = isInVisibleChain(); + info["enabled"] = getEnabled(); + info["enabled_chain"] = isInEnabledChain(); + info["available"] = isAvailable(); + LLRect rect(calcScreenRect()); + info["rect"] = LLSDMap("left", rect.mLeft)("top", rect.mTop) + ("right", rect.mRight)("bottom", rect.mBottom); +} diff --git a/indra/llui/llview.h b/indra/llui/llview.h index 33d345beff..1c35349510 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -34,6 +34,7 @@ #include "stdtypes.h" #include "llcoord.h" #include "llfontgl.h" +#include "llhandle.h" #include "llmortician.h" #include "llmousehandler.h" #include "llstring.h" @@ -49,6 +50,8 @@ #include "llfocusmgr.h" #include <list> +#include <boost/function.hpp> +#include <boost/noncopyable.hpp> class LLSD; @@ -94,13 +97,14 @@ private: static std::vector<LLViewDrawContext*> sDrawContextStack; }; -class LLViewWidgetRegistry : public LLChildRegistry<LLViewWidgetRegistry> -{}; - -class LLView : public LLMouseHandler, public LLMortician, public LLFocusableElement +class LLView +: public LLMouseHandler, // handles mouse events + public LLFocusableElement, // handles keyboard events + public LLMortician, // lazy deletion + public LLHandleProvider<LLView> // passes out weak references to self { public: - struct Follows : public LLInitParam::Choice<Follows> + struct Follows : public LLInitParam::ChoiceBlock<Follows> { Alternative<std::string> string; Alternative<U32> flags; @@ -116,7 +120,8 @@ public: visible, mouse_opaque, use_bounding_rect, - from_xui; + from_xui, + focus_root; Optional<S32> tab_group, default_tab_group; @@ -148,7 +153,8 @@ public: Params(); }; - typedef LLViewWidgetRegistry child_registry_t; + // most widgets are valid children of LLView + typedef LLDefaultChildRegistry child_registry_t; void initFromParams(const LLView::Params&); @@ -238,7 +244,7 @@ public: ECursorType getHoverCursor() { return mHoverCursor; } - const std::string& getToolTip() const { return mToolTipMsg.getString(); } + virtual const std::string getToolTip() const { return mToolTipMsg.getString(); } void sendChildToFront(LLView* child); void sendChildToBack(LLView* child); @@ -286,7 +292,7 @@ public: void setAllChildrenEnabled(BOOL b); virtual void setVisible(BOOL visible); - BOOL getVisible() const { return mVisible; } + const BOOL& getVisible() const { return mVisible; } virtual void setEnabled(BOOL enabled); BOOL getEnabled() const { return mEnabled; } /// 'available' in this context means 'visible and enabled': in other @@ -304,8 +310,6 @@ public: void popVisible() { setVisible(mLastVisible); } BOOL getLastVisible() const { return mLastVisible; } - LLHandle<LLView> getHandle() { mHandle.bind(this); return mHandle; } - U32 getFollows() const { return mReshapeFlags; } BOOL followsLeft() const { return mReshapeFlags & FOLLOWS_LEFT; } BOOL followsRight() const { return mReshapeFlags & FOLLOWS_RIGHT; } @@ -366,8 +370,8 @@ public: virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); virtual void translate( S32 x, S32 y ); void setOrigin( S32 x, S32 y ) { mRect.translate( x - mRect.mLeft, y - mRect.mBottom ); } - BOOL translateIntoRect( const LLRect& constraint, BOOL allow_partial_outside ); - BOOL translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, BOOL allow_partial_outside ); + BOOL translateIntoRect( const LLRect& constraint, S32 min_overlap_pixels = S32_MAX); + BOOL translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, S32 min_overlap_pixels = S32_MAX); void centerWithin(const LLRect& bounds); void setShape(const LLRect& new_rect, bool by_user = false); @@ -405,21 +409,16 @@ public: BOOL blockMouseEvent(S32 x, S32 y) const; // See LLMouseHandler virtuals for screenPointToLocal and localPointToScreen - BOOL localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, LLView* other_view) const; - BOOL localRectToOtherView( const LLRect& local, LLRect* other, LLView* other_view ) const; + BOOL localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, const LLView* other_view) const; + BOOL localRectToOtherView( const LLRect& local, LLRect* other, const LLView* other_view ) const; void screenRectToLocal( const LLRect& screen, LLRect* local ) const; void localRectToScreen( const LLRect& local, LLRect* screen ) const; LLControlVariable *findControl(const std::string& name); - // Moved setValue(), getValue(), setControlValue(), setControlName(), - // controlListener() to LLUICtrl because an LLView is NOT assumed to - // contain a value. If that's what you want, use LLUICtrl instead. -// virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); - const child_list_t* getChildList() const { return &mChildList; } - const child_list_const_iter_t beginChild() { return mChildList.begin(); } - const child_list_const_iter_t endChild() { return mChildList.end(); } + child_list_const_iter_t beginChild() const { return mChildList.begin(); } + child_list_const_iter_t endChild() const { return mChildList.end(); } // LLMouseHandler functions // Default behavior is to pass events to children @@ -434,18 +433,21 @@ public: /*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ std::string getName() const; + /*virtual*/ const std::string& getName() const; /*virtual*/ void onMouseCaptureLost(); /*virtual*/ BOOL hasMouseCapture(); /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const; /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const; - virtual LLView* childFromPoint(S32 x, S32 y); + virtual LLView* childFromPoint(S32 x, S32 y, bool recur=false); // view-specific handlers virtual void onMouseEnter(S32 x, S32 y, MASK mask); virtual void onMouseLeave(S32 x, S32 y, MASK mask); + std::string getPathname() const; + // static method handles NULL pointer too + static std::string getPathname(const LLView*); template <class T> T* findChild(const std::string& name, BOOL recurse = TRUE) const { @@ -466,12 +468,22 @@ public: template <class T> T* getDefaultWidget(const std::string& name) const { - default_widget_map_t::const_iterator found_it = getDefaultWidgetMap().find(name); - if (found_it == getDefaultWidgetMap().end()) + LLView* widgetp = getDefaultWidgetContainer().findChildView(name); + return dynamic_cast<T*>(widgetp); + } + + template <class T> T* getParentByType() const + { + LLView* parent = getParent(); + while(parent) { - return NULL; + if (dynamic_cast<T*>(parent)) + { + return static_cast<T*>(parent); + } + parent = parent->getParent(); } - return dynamic_cast<T*>(found_it->second); + return NULL; } ////////////////////////////////////////////// @@ -489,12 +501,11 @@ public: // return query for iterating over focus roots in tab order static const LLCtrlQuery & getFocusRootsQuery(); - static void deleteViewByHandle(LLHandle<LLView> handle); static LLWindow* getWindow(void) { return LLUI::sWindow; } // Set up params after XML load before calling new(), // usually to adjust layout. - static void applyXUILayout(Params& p, LLView* parent); + static void applyXUILayout(Params& p, LLView* parent, LLRect layout_rect = LLRect()); // For re-export of floaters and panels, convert the coordinate system // to be top-left based. @@ -518,11 +529,17 @@ public: virtual S32 notify(const LLSD& info) { return 0;}; static const LLViewDrawContext& getDrawContext(); + + // Returns useful information about this ui widget. + LLSD getInfo(void); protected: void drawDebugRect(); void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0, BOOL force_draw = FALSE); void drawChildren(); + bool visibleAndContains(S32 local_x, S32 local_Y); + bool visibleEnabledAndContains(S32 local_x, S32 local_y); + void logMouseEvent(); LLView* childrenHandleKey(KEY key, MASK mask); LLView* childrenHandleUnicodeChar(llwchar uni_char); @@ -545,17 +562,34 @@ protected: LLView* childrenHandleToolTip(S32 x, S32 y, MASK mask); ECursorType mHoverCursor; - + + virtual void addInfo(LLSD & info); private: + template <typename METHOD, typename XDATA> + LLView* childrenHandleMouseEvent(const METHOD& method, S32 x, S32 y, XDATA extra, bool allow_mouse_block = true); + + template <typename METHOD, typename CHARTYPE> + LLView* childrenHandleCharEvent(const std::string& desc, const METHOD& method, + CHARTYPE c, MASK mask); + + // adapter to blur distinction between handleKey() and handleUnicodeChar() + // for childrenHandleCharEvent() + BOOL handleUnicodeCharWithDummyMask(llwchar uni_char, MASK /* dummy */, BOOL from_parent) + { + return handleUnicodeChar(uni_char, from_parent); + } + LLView* mParentView; child_list_t mChildList; - std::string mName; // location in pixels, relative to surrounding structure, bottom,left=0,0 + BOOL mVisible; LLRect mRect; LLRect mBoundingRect; + std::string mLayout; + std::string mName; U32 mReshapeFlags; @@ -574,22 +608,49 @@ private: BOOL mIsFocusRoot; BOOL mUseBoundingRect; // hit test against bounding rectangle that includes all child elements - LLRootHandle<LLView> mHandle; BOOL mLastVisible; - BOOL mVisible; - S32 mNextInsertionOrdinal; + bool mInDraw; + static LLWindow* sWindow; // All root views must know about their window. typedef std::map<std::string, LLView*> default_widget_map_t; // allocate this map no demand, as it is rarely needed - mutable default_widget_map_t* mDefaultWidgets; + mutable LLView* mDefaultWidgets; - default_widget_map_t& getDefaultWidgetMap() const; + LLView& getDefaultWidgetContainer() const; + + // This allows special mouse-event targeting logic for testing. + typedef boost::function<bool(const LLView*, S32 x, S32 y)> DrilldownFunc; + static DrilldownFunc sDrilldown; public: + // This is the only public accessor to alter sDrilldown. This is not + // an accident. The intended usage pattern is like: + // { + // LLView::TemporaryDrilldownFunc scoped_func(myfunctor); + // // ... test with myfunctor ... + // } // exiting block restores original LLView::sDrilldown + class TemporaryDrilldownFunc: public boost::noncopyable + { + public: + TemporaryDrilldownFunc(const DrilldownFunc& func): + mOldDrilldown(sDrilldown) + { + sDrilldown = func; + } + + ~TemporaryDrilldownFunc() + { + sDrilldown = mOldDrilldown; + } + + private: + DrilldownFunc mOldDrilldown; + }; + // Depth in view hierarchy during rendering static S32 sDepth; @@ -654,7 +715,7 @@ template <class T> T* LLView::getChild(const std::string& name, BOOL recurse) co return NULL; } - getDefaultWidgetMap()[name] = result; + getDefaultWidgetContainer().addChild(result); } } return result; diff --git a/indra/llui/llviewborder.cpp b/indra/llui/llviewborder.cpp index 89cd34c37c..919267dcc6 100644 --- a/indra/llui/llviewborder.cpp +++ b/indra/llui/llviewborder.cpp @@ -28,6 +28,7 @@ #include "llrender.h" #include "llfocusmgr.h" #include "lluictrlfactory.h" +#include "lluiimage.h" static LLDefaultChildRegistry::Register<LLViewBorder> r("view_border"); @@ -56,9 +57,6 @@ LLViewBorder::Params::Params() { addSynonym(border_thickness, "thickness"); addSynonym(render_style, "style"); - name = "view_border"; - mouse_opaque = false; - follows.flags = FOLLOWS_ALL; } diff --git a/indra/llui/llviewinject.cpp b/indra/llui/llviewinject.cpp new file mode 100644 index 0000000000..46c5839f8e --- /dev/null +++ b/indra/llui/llviewinject.cpp @@ -0,0 +1,49 @@ +/** + * @file llviewinject.cpp + * @author Nat Goodspeed + * @date 2011-08-16 + * @brief Implementation for llviewinject. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Copyright (c) 2011, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llviewinject.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +llview::TargetEvent::TargetEvent(LLView* view) +{ + // Walk up the view tree from target LLView to the root (NULL). If + // passed NULL, iterate 0 times. + for (; view; view = view->getParent()) + { + // At each level, operator() is going to ask: for a particular parent + // LLView*, which of its children should I select? So for this view's + // parent, select this view. + mChildMap[view->getParent()] = view; + } +} + +bool llview::TargetEvent::operator()(const LLView* view, S32 /*x*/, S32 /*y*/) const +{ + // We are being called to decide whether to direct an incoming mouse event + // to this child view. (Normal LLView processing is to check whether the + // incoming (x, y) is within the view.) Look up the parent to decide + // whether, for that parent, this is the previously-selected child. + ChildMap::const_iterator found(mChildMap.find(view->getParent())); + // If we're looking at a child whose parent isn't even in the map, never + // mind. + if (found == mChildMap.end()) + { + return false; + } + // So, is this the predestined child for this parent? + return (view == found->second); +} diff --git a/indra/llui/llviewinject.h b/indra/llui/llviewinject.h new file mode 100644 index 0000000000..0de3d155c4 --- /dev/null +++ b/indra/llui/llviewinject.h @@ -0,0 +1,56 @@ +/** + * @file llviewinject.h + * @author Nat Goodspeed + * @date 2011-08-16 + * @brief Supplemental LLView functionality used for simulating UI events. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Copyright (c) 2011, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLVIEWINJECT_H) +#define LL_LLVIEWINJECT_H + +#include "llview.h" +#include <map> + +namespace llview +{ + + /** + * TargetEvent is a callable with state, specifically intended for use as + * an LLView::TemporaryDrilldownFunc. Instantiate it with the desired + * target LLView*; pass it to a TemporaryDrilldownFunc instance; + * TargetEvent::operator() will then attempt to direct subsequent mouse + * events to the desired target LLView*. (This is an "attempt" because + * LLView will still balk unless the target LLView and every parent are + * visible and enabled.) + */ + class TargetEvent + { + public: + /** + * Construct TargetEvent with the desired target LLView*. (See + * LLUI::resolvePath() to obtain an LLView* given a string pathname.) + * This sets up for operator(). + */ + TargetEvent(LLView* view); + + /** + * This signature must match LLView::DrilldownFunc. When you install + * this TargetEvent instance using LLView::TemporaryDrilldownFunc, + * LLView will call this method to decide whether to propagate an + * incoming mouse event to the passed child LLView*. + */ + bool operator()(const LLView*, S32 x, S32 y) const; + + private: + // For a given parent LLView, identify which child to select. + typedef std::map<LLView*, LLView*> ChildMap; + ChildMap mChildMap; + }; + +} // llview namespace + +#endif /* ! defined(LL_LLVIEWINJECT_H) */ diff --git a/indra/llui/llwindowshade.cpp b/indra/llui/llwindowshade.cpp new file mode 100644 index 0000000000..f5c463c961 --- /dev/null +++ b/indra/llui/llwindowshade.cpp @@ -0,0 +1,392 @@ +/** + * @file LLWindowShade.cpp + * @brief Notification dialog that slides down and optionally disabled a piece of UI + * + * $LicenseInfo:firstyear=2006&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 "linden_common.h" +#include "llwindowshade.h" + +#include "lllayoutstack.h" +#include "lltextbox.h" +#include "lliconctrl.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "lllineeditor.h" + +const S32 MIN_NOTIFICATION_AREA_HEIGHT = 30; +const S32 MAX_NOTIFICATION_AREA_HEIGHT = 100; + +static LLDefaultChildRegistry::Register<LLWindowShade> r("window_shade"); + +LLWindowShade::Params::Params() +: bg_image("bg_image"), + modal("modal", false), + text_color("text_color"), + shade_color("shade_color"), + can_close("can_close", true) +{ + changeDefault(mouse_opaque, false); +} + +LLWindowShade::LLWindowShade(const LLWindowShade::Params& params) +: LLUICtrl(params), + mModal(params.modal), + mFormHeight(0), + mTextColor(params.text_color) +{ + setFocusRoot(true); +} + +void LLWindowShade::initFromParams(const LLWindowShade::Params& params) +{ + LLUICtrl::initFromParams(params); + + LLLayoutStack::Params layout_p; + layout_p.name = "notification_stack"; + layout_p.rect = params.rect; + layout_p.follows.flags = FOLLOWS_ALL; + layout_p.mouse_opaque = false; + layout_p.orientation = LLLayoutStack::VERTICAL; + layout_p.border_size = 0; + + LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p); + addChild(stackp); + + LLLayoutPanel::Params panel_p; + panel_p.rect = LLRect(0, MIN_NOTIFICATION_AREA_HEIGHT, 800, 0); + panel_p.name = "notification_area"; + panel_p.visible = false; + panel_p.user_resize = false; + panel_p.background_visible = true; + panel_p.bg_alpha_image = params.bg_image; + panel_p.auto_resize = false; + LLLayoutPanel* notification_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); + stackp->addChild(notification_panel); + + panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); + panel_p.auto_resize = true; + panel_p.user_resize = false; + panel_p.rect = params.rect; + panel_p.name = "background_area"; + panel_p.mouse_opaque = false; + panel_p.background_visible = false; + panel_p.bg_alpha_color = params.shade_color; + LLLayoutPanel* dummy_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); + stackp->addChild(dummy_panel); + + layout_p = LLUICtrlFactory::getDefaultParams<LLLayoutStack>(); + layout_p.rect = LLRect(0, 30, 800, 0); + layout_p.follows.flags = FOLLOWS_ALL; + layout_p.orientation = LLLayoutStack::HORIZONTAL; + stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p); + notification_panel->addChild(stackp); + + panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); + panel_p.rect.height = 30; + LLLayoutPanel* panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); + stackp->addChild(panel); + + LLIconCtrl::Params icon_p; + icon_p.name = "notification_icon"; + icon_p.rect = LLRect(5, 25, 21, 10); + panel->addChild(LLUICtrlFactory::create<LLIconCtrl>(icon_p)); + + LLTextBox::Params text_p; + text_p.rect = LLRect(31, 23, panel->getRect().getWidth() - 5, 3); + text_p.follows.flags = FOLLOWS_ALL; + text_p.text_color = mTextColor; + text_p.font = LLFontGL::getFontSansSerifSmall(); + text_p.font.style = "BOLD"; + text_p.name = "notification_text"; + text_p.use_ellipses = true; + text_p.wrap = true; + panel->addChild(LLUICtrlFactory::create<LLTextBox>(text_p)); + + panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); + panel_p.auto_resize = false; + panel_p.user_resize = false; + panel_p.name="form_elements"; + panel_p.rect = LLRect(0, MIN_NOTIFICATION_AREA_HEIGHT, 130, 0); + LLLayoutPanel* form_elements_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); + stackp->addChild(form_elements_panel); + + panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); + panel_p.auto_resize = false; + panel_p.user_resize = false; + panel_p.rect = LLRect(0, MIN_NOTIFICATION_AREA_HEIGHT, 25, 0); + panel_p.name = "close_panel"; + LLLayoutPanel* close_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); + stackp->addChild(close_panel); + + LLButton::Params button_p; + button_p.name = "close_notification"; + button_p.rect = LLRect(5, 23, 21, 7); + button_p.image_color.control="DkGray_66"; + button_p.image_unselected.name="Icon_Close_Foreground"; + button_p.image_selected.name="Icon_Close_Press"; + button_p.click_callback.function = boost::bind(&LLWindowShade::onCloseNotification, this); + + close_panel->addChild(LLUICtrlFactory::create<LLButton>(button_p)); + + close_panel->setVisible(params.can_close); +} + +void LLWindowShade::draw() +{ + LLRect message_rect = getChild<LLTextBox>("notification_text")->getTextBoundingRect(); + + LLLayoutPanel* notification_area = getChild<LLLayoutPanel>("notification_area"); + + notification_area->reshape(notification_area->getRect().getWidth(), + llclamp(message_rect.getHeight() + 15, + llmax(mFormHeight, MIN_NOTIFICATION_AREA_HEIGHT), + MAX_NOTIFICATION_AREA_HEIGHT)); + + LLUICtrl::draw(); + + while(!mNotifications.empty() && !mNotifications.back()->isActive()) + { + mNotifications.pop_back(); + // go ahead and hide + hide(); + } + + if (mNotifications.empty()) + { + hide(); + } + else if (notification_area->getVisibleAmount() < 0.01f) + { + displayLatestNotification(); + } + + if (!notification_area->getVisible() && (notification_area->getVisibleAmount() < 0.001f)) + { + getChildRef<LLLayoutPanel>("background_area").setBackgroundVisible(false); + setMouseOpaque(false); + } +} + +void LLWindowShade::hide() +{ + getChildRef<LLLayoutPanel>("notification_area").setVisible(false); +} + +void LLWindowShade::onCloseNotification() +{ + if (!mNotifications.empty()) + LLNotifications::instance().cancel(mNotifications.back()); +} + +void LLWindowShade::onClickIgnore(LLUICtrl* ctrl) +{ + LLNotificationPtr notify = getCurrentNotification(); + if (!notify) return; + + bool check = ctrl->getValue().asBoolean(); + if (notify->getForm()->getIgnoreType() == LLNotificationForm::IGNORE_SHOW_AGAIN) + { + // question was "show again" so invert value to get "ignore" + check = !check; + } + notify->setIgnored(check); +} + +void LLWindowShade::onClickNotificationButton(const std::string& name) +{ + LLNotificationPtr notify = getCurrentNotification(); + if (!notify) return; + + mNotificationResponse[name] = true; + + notify->respond(mNotificationResponse); +} + +void LLWindowShade::onEnterNotificationText(LLUICtrl* ctrl, const std::string& name) +{ + mNotificationResponse[name] = ctrl->getValue().asString(); +} + +void LLWindowShade::show(LLNotificationPtr notification) +{ + mNotifications.push_back(notification); + + displayLatestNotification(); +} + +void LLWindowShade::displayLatestNotification() +{ + if (mNotifications.empty()) return; + + LLNotificationPtr notification = mNotifications.back(); + + LLSD payload = notification->getPayload(); + + LLNotificationFormPtr formp = notification->getForm(); + LLLayoutPanel& notification_area = getChildRef<LLLayoutPanel>("notification_area"); + notification_area.getChild<LLUICtrl>("notification_icon")->setValue(notification->getIcon()); + notification_area.getChild<LLUICtrl>("notification_text")->setValue(notification->getMessage()); + notification_area.getChild<LLUICtrl>("notification_text")->setToolTip(notification->getMessage()); + + LLNotificationForm::EIgnoreType ignore_type = formp->getIgnoreType(); + LLLayoutPanel& form_elements = notification_area.getChildRef<LLLayoutPanel>("form_elements"); + form_elements.deleteAllChildren(); + form_elements.reshape(form_elements.getRect().getWidth(), MIN_NOTIFICATION_AREA_HEIGHT); + + const S32 FORM_PADDING_HORIZONTAL = 10; + const S32 FORM_PADDING_VERTICAL = 3; + const S32 WIDGET_HEIGHT = 24; + const S32 LINE_EDITOR_WIDTH = 120; + S32 cur_x = FORM_PADDING_HORIZONTAL; + S32 cur_y = FORM_PADDING_VERTICAL + WIDGET_HEIGHT; + S32 form_width = cur_x; + + if (ignore_type != LLNotificationForm::IGNORE_NO) + { + LLCheckBoxCtrl::Params checkbox_p; + checkbox_p.name = "ignore_check"; + checkbox_p.rect = LLRect(cur_x, cur_y, cur_x, cur_y - WIDGET_HEIGHT); + checkbox_p.label = formp->getIgnoreMessage(); + checkbox_p.label_text.text_color = LLColor4::black; + checkbox_p.commit_callback.function = boost::bind(&LLWindowShade::onClickIgnore, this, _1); + checkbox_p.initial_value = formp->getIgnored(); + + LLCheckBoxCtrl* check = LLUICtrlFactory::create<LLCheckBoxCtrl>(checkbox_p); + check->setRect(check->getBoundingRect()); + form_elements.addChild(check); + cur_x = check->getRect().mRight + FORM_PADDING_HORIZONTAL; + form_width = llmax(form_width, cur_x); + } + + for (S32 i = 0; i < formp->getNumElements(); i++) + { + LLSD form_element = formp->getElement(i); + std::string type = form_element["type"].asString(); + if (type == "button") + { + LLButton::Params button_p; + button_p.name = form_element["name"]; + button_p.label = form_element["text"]; + button_p.rect = LLRect(cur_x, cur_y, cur_x, cur_y - WIDGET_HEIGHT); + button_p.click_callback.function = boost::bind(&LLWindowShade::onClickNotificationButton, this, form_element["name"].asString()); + button_p.auto_resize = true; + + LLButton* button = LLUICtrlFactory::create<LLButton>(button_p); + button->autoResize(); + form_elements.addChild(button); + + if (form_element["default"].asBoolean()) + { + form_elements.setDefaultBtn(button); + } + + cur_x = button->getRect().mRight + FORM_PADDING_HORIZONTAL; + form_width = llmax(form_width, cur_x); + } + else if (type == "text" || type == "password") + { + // if not at beginning of line... + if (cur_x != FORM_PADDING_HORIZONTAL) + { + // start new line + cur_x = FORM_PADDING_HORIZONTAL; + cur_y -= WIDGET_HEIGHT + FORM_PADDING_VERTICAL; + } + LLTextBox::Params label_p; + label_p.name = form_element["name"].asString() + "_label"; + label_p.rect = LLRect(cur_x, cur_y, cur_x + LINE_EDITOR_WIDTH, cur_y - WIDGET_HEIGHT); + label_p.initial_value = form_element["text"]; + label_p.text_color = mTextColor; + label_p.font_valign = LLFontGL::VCENTER; + label_p.v_pad = 5; + LLTextBox* textbox = LLUICtrlFactory::create<LLTextBox>(label_p); + textbox->reshapeToFitText(); + textbox->reshape(textbox->getRect().getWidth(), MIN_NOTIFICATION_AREA_HEIGHT - 2 * FORM_PADDING_VERTICAL); + form_elements.addChild(textbox); + cur_x = textbox->getRect().mRight + FORM_PADDING_HORIZONTAL; + + LLLineEditor::Params line_p; + line_p.name = form_element["name"]; + line_p.keystroke_callback = boost::bind(&LLWindowShade::onEnterNotificationText, this, _1, form_element["name"].asString()); + line_p.is_password = type == "password"; + line_p.rect = LLRect(cur_x, cur_y, cur_x + LINE_EDITOR_WIDTH, cur_y - WIDGET_HEIGHT); + + LLLineEditor* line_editor = LLUICtrlFactory::create<LLLineEditor>(line_p); + form_elements.addChild(line_editor); + form_width = llmax(form_width, cur_x + LINE_EDITOR_WIDTH + FORM_PADDING_HORIZONTAL); + + // reset to start of next line + cur_x = FORM_PADDING_HORIZONTAL; + cur_y -= WIDGET_HEIGHT + FORM_PADDING_VERTICAL; + } + } + + mFormHeight = form_elements.getRect().getHeight() - (cur_y - WIDGET_HEIGHT - FORM_PADDING_VERTICAL); + form_elements.reshape(form_width, mFormHeight); + form_elements.setMinDim(form_width); + + // move all form elements back onto form surface + S32 delta_y = WIDGET_HEIGHT + FORM_PADDING_VERTICAL - cur_y; + for (child_list_const_iter_t it = form_elements.getChildList()->begin(), end_it = form_elements.getChildList()->end(); + it != end_it; + ++it) + { + (*it)->translate(0, delta_y); + } + + getChildRef<LLLayoutPanel>("notification_area").setVisible(true); + getChildRef<LLLayoutPanel>("background_area").setBackgroundVisible(mModal); + + setMouseOpaque(mModal); +} + +void LLWindowShade::setBackgroundImage(LLUIImage* image) +{ + getChild<LLLayoutPanel>("notification_area")->setTransparentImage(image); +} + +void LLWindowShade::setTextColor(LLColor4 color) +{ + getChild<LLTextBox>("notification_text")->setColor(color); +} + +bool LLWindowShade::isShown() const +{ + return getChildRef<LLLayoutPanel>("notification_area").getVisible(); +} + +void LLWindowShade::setCanClose(bool can_close) +{ + getChildView("close_panel")->setVisible(can_close); +} + +LLNotificationPtr LLWindowShade::getCurrentNotification() +{ + if (mNotifications.empty()) + { + return LLNotificationPtr(); + } + return mNotifications.back(); +} + diff --git a/indra/llui/llwindowshade.h b/indra/llui/llwindowshade.h new file mode 100644 index 0000000000..6d753d1161 --- /dev/null +++ b/indra/llui/llwindowshade.h @@ -0,0 +1,78 @@ +/** + * @file llwindowshade.h + * @brief Notification dialog that slides down and optionally disabled a piece of UI + * + * $LicenseInfo:firstyear=2006&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$ + */ + +#ifndef LL_LLWINDOWSHADE_H +#define LL_LLWINDOWSHADE_H + +#include "lluictrl.h" +#include "llnotifications.h" +#include "lluiimage.h" + +class LLWindowShade : public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<LLUIImage*> bg_image; + Optional<LLUIColor> text_color, + shade_color; + Optional<bool> modal, + can_close; + + Params(); + }; + + void show(LLNotificationPtr); + /*virtual*/ void draw(); + void hide(); + + bool isShown() const; + + void setBackgroundImage(LLUIImage* image); + void setTextColor(LLColor4 color); + void setCanClose(bool can_close); + +private: + void displayLatestNotification(); + LLNotificationPtr getCurrentNotification(); + friend class LLUICtrlFactory; + + LLWindowShade(const Params& p); + void initFromParams(const Params& params); + + void onCloseNotification(); + void onClickNotificationButton(const std::string& name); + void onEnterNotificationText(LLUICtrl* ctrl, const std::string& name); + void onClickIgnore(LLUICtrl* ctrl); + + std::vector<LLNotificationPtr> mNotifications; + LLSD mNotificationResponse; + bool mModal; + S32 mFormHeight; + LLUIColor mTextColor; +}; + +#endif // LL_LLWINDOWSHADE_H diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index f30704cb22..c75df86891 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -30,6 +30,7 @@ #include "llavatarnamecache.h" #include "llcachename.h" #include "lluuid.h" +#include "message.h" #include <string> @@ -104,8 +105,6 @@ LLStyle::Params::Params() namespace LLInitParam { - BaseBlock::BaseBlock() {} - BaseBlock::~BaseBlock() {} Param::Param(BaseBlock* enclosing_block) : mIsProvided(false) { @@ -113,32 +112,29 @@ namespace LLInitParam const U8* block_addr = reinterpret_cast<const U8*>(enclosing_block); mEnclosingBlockOffset = (U16)(my_addr - block_addr); } - void BaseBlock::setLastChangedParam(const Param& last_param, bool user_provided) {} - void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptor& in_param, const char* char_name){} + void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name){} + void BaseBlock::addSynonym(Param& param, const std::string& synonym) {} param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;} void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) { descriptor.mCurrentBlockPtr = this; } - bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack){ return true; } - bool BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t name_stack, const LLInitParam::BaseBlock* diff_block) const { return true; } - bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack) const { return true; } - bool BaseBlock::merge(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } + bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; } + void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {} + bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_value, S32 max_value) const { return true; } + bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } bool BaseBlock::validateBlock(bool emit_errors) const { return true; } - TypedParam<LLUIColor >::TypedParam(BlockDescriptor& descriptor, const char* name, const LLUIColor& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, name, value, func, min_count, max_count) + ParamValue<LLUIColor, TypeValues<LLUIColor> >::ParamValue(const LLUIColor& color) + : super_t(color) {} - void TypedParam<LLUIColor>::setValueFromBlock() const + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateValueFromBlock() {} - void TypedParam<LLUIColor>::setBlockFromValue() - {} - - void TypeValues<LLUIColor>::declareValues() + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateBlockFromValue(bool) {} bool ParamCompare<const LLFontGL*, false>::equals(const LLFontGL* a, const LLFontGL* b) @@ -146,14 +142,14 @@ namespace LLInitParam return false; } - TypedParam<const LLFontGL*>::TypedParam(BlockDescriptor& descriptor, const char* _name, const LLFontGL*const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, _name, value, func, min_count, max_count) + ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::ParamValue(const LLFontGL* fontp) + : super_t(fontp) {} - void TypedParam<const LLFontGL*>::setValueFromBlock() const + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateValueFromBlock() {} - void TypedParam<const LLFontGL*>::setBlockFromValue() + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateBlockFromValue(bool) {} void TypeValues<LLFontGL::HAlign>::declareValues() @@ -165,10 +161,10 @@ namespace LLInitParam void TypeValues<LLFontGL::ShadowType>::declareValues() {} - void TypedParam<LLUIImage*>::setValueFromBlock() const + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateValueFromBlock() {} - void TypedParam<LLUIImage*>::setBlockFromValue() + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateBlockFromValue(bool) {} @@ -191,3 +187,20 @@ LLFontGL* LLFontGL::getFontDefault() { return NULL; } + +char const* const _PREHASH_AgentData = (char *)"AgentData"; +char const* const _PREHASH_AgentID = (char *)"AgentID"; + +LLHost LLHost::invalid(INVALID_PORT,INVALID_HOST_IP_ADDRESS); + +LLMessageSystem* gMessageSystem = NULL; + +// +// Stub implementation for LLMessageSystem +// +void LLMessageSystem::newMessage(const char *name) { } +void LLMessageSystem::nextBlockFast(const char *blockname) { } +void LLMessageSystem::nextBlock(const char *blockname) { } +void LLMessageSystem::addUUIDFast( const char *varname, const LLUUID& uuid) { } +void LLMessageSystem::addUUID( const char *varname, const LLUUID& uuid) { } +S32 LLMessageSystem::sendReliable(const LLHost &host) { return 0; } diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 5c6623da61..c1fb050206 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -27,6 +27,7 @@ #include "linden_common.h" #include "../llurlentry.h" +#include "../lluictrl.h" #include "llurlentry_stub.cpp" #include "lltut.h" #include "../lluicolortable.h" @@ -34,6 +35,14 @@ #include <boost/regex.hpp> +typedef std::map<std::string, LLControlGroup*> settings_map_t; +settings_map_t LLUI::sSettingGroups; + +BOOL LLControlGroup::getBOOL(const std::string& name) +{ + return false; +} + LLUIColor LLUIColorTable::getColor(const std::string& name, const LLColor4& default_color) const { return LLUIColor(); @@ -61,6 +70,21 @@ S32 LLUIImage::getHeight() const return 0; } +namespace LLInitParam +{ + BlockDescriptor::BlockDescriptor() {} + ParamDescriptor::ParamDescriptor(param_handle_t p, + merge_func_t merge_func, + deserialize_func_t deserialize_func, + serialize_func_t serialize_func, + validation_func_t validation_func, + inspect_func_t inspect_func, + S32 min_count, + S32 max_count){} + ParamDescriptor::~ParamDescriptor() {} + +} + namespace tut { struct LLUrlEntryData @@ -94,6 +118,45 @@ namespace tut ensure_equals(testname, url, expected); } + void dummyCallback(const std::string &url, const std::string &label, const std::string& icon) + { + } + + void testLabel(const std::string &testname, LLUrlEntryBase &entry, + const char *text, const std::string &expected) + { + boost::regex regex = entry.getPattern(); + std::string label = ""; + boost::cmatch result; + bool found = boost::regex_search(text, result, regex); + if (found) + { + S32 start = static_cast<U32>(result[0].first - text); + S32 end = static_cast<U32>(result[0].second - text); + std::string url = std::string(text+start, end-start); + label = entry.getLabel(url, boost::bind(dummyCallback, _1, _2, _3)); + } + ensure_equals(testname, label, expected); + } + + void testLocation(const std::string &testname, LLUrlEntryBase &entry, + const char *text, const std::string &expected) + { + boost::regex regex = entry.getPattern(); + std::string location = ""; + boost::cmatch result; + bool found = boost::regex_search(text, result, regex); + if (found) + { + S32 start = static_cast<U32>(result[0].first - text); + S32 end = static_cast<U32>(result[0].second - text); + std::string url = std::string(text+start, end-start); + location = entry.getLocation(url); + } + ensure_equals(testname, location, expected); + } + + template<> template<> void object::test<1>() { @@ -688,4 +751,114 @@ namespace tut "<nolink>My Object</nolink>", "My Object"); } + + template<> template<> + void object::test<13>() + { + // + // test LLUrlEntryRegion - secondlife:///app/region/<location> URLs + // + LLUrlEntryRegion url; + + // Regex tests. + testRegex("no valid region", url, + "secondlife:///app/region/", + ""); + + testRegex("invalid coords", url, + "secondlife:///app/region/Korea2/a/b/c", + "secondlife:///app/region/Korea2/"); // don't count invalid coords + + testRegex("Ahern (50,50,50) [1]", url, + "secondlife:///app/region/Ahern/50/50/50/", + "secondlife:///app/region/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [2]", url, + "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", + "secondlife:///app/region/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [3]", url, + "XXX secondlife:///app/region/Ahern/50/50/50 XXX", + "secondlife:///app/region/Ahern/50/50/50"); + + testRegex("Ahern (50,50,50) multicase", url, + "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", + "secondlife:///app/region/Ahern/50/50/50/"); + + testRegex("Ahern (50,50) [1]", url, + "XXX secondlife:///app/region/Ahern/50/50/ XXX", + "secondlife:///app/region/Ahern/50/50/"); + + testRegex("Ahern (50,50) [2]", url, + "XXX secondlife:///app/region/Ahern/50/50 XXX", + "secondlife:///app/region/Ahern/50/50"); + + // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat + testRegex("Region with brackets", url, + "XXX secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30 XXX", + "secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30"); + + // DEV-35459: SLURLs and teleport Links not parsed properly + testRegex("Region with quote", url, + "XXX secondlife:///app/region/A'ksha%20Oasis/41/166/701 XXX", + "secondlife:///app/region/A%27ksha%20Oasis/41/166/701"); + + // Rendering tests. + testLabel("Render /app/region/Ahern/50/50/50/", url, + "secondlife:///app/region/Ahern/50/50/50/", + "Ahern (50,50,50)"); + + testLabel("Render /app/region/Ahern/50/50/50", url, + "secondlife:///app/region/Ahern/50/50/50", + "Ahern (50,50,50)"); + + testLabel("Render /app/region/Ahern/50/50/", url, + "secondlife:///app/region/Ahern/50/50/", + "Ahern (50,50)"); + + testLabel("Render /app/region/Ahern/50/50", url, + "secondlife:///app/region/Ahern/50/50", + "Ahern (50,50)"); + + testLabel("Render /app/region/Ahern/50/", url, + "secondlife:///app/region/Ahern/50/", + "Ahern (50)"); + + testLabel("Render /app/region/Ahern/50", url, + "secondlife:///app/region/Ahern/50", + "Ahern (50)"); + + testLabel("Render /app/region/Ahern/", url, + "secondlife:///app/region/Ahern/", + "Ahern"); + + testLabel("Render /app/region/Ahern/ within context", url, + "XXX secondlife:///app/region/Ahern/ XXX", + "Ahern"); + + testLabel("Render /app/region/Ahern", url, + "secondlife:///app/region/Ahern", + "Ahern"); + + testLabel("Render /app/region/Ahern within context", url, + "XXX secondlife:///app/region/Ahern XXX", + "Ahern"); + + testLabel("Render /app/region/Product%20Engine/", url, + "secondlife:///app/region/Product%20Engine/", + "Product Engine"); + + testLabel("Render /app/region/Product%20Engine", url, + "secondlife:///app/region/Product%20Engine", + "Product Engine"); + + // Location parsing texts. + testLocation("Location /app/region/Ahern/50/50/50/", url, + "secondlife:///app/region/Ahern/50/50/50/", + "Ahern"); + + testLocation("Location /app/region/Product%20Engine", url, + "secondlife:///app/region/Product%20Engine", + "Product Engine"); + } } diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index fdaab00f18..7183413463 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -63,14 +63,21 @@ S32 LLUIImage::getHeight() const namespace LLInitParam { - BaseBlock::BaseBlock() {} - BaseBlock::~BaseBlock() {} - - void BaseBlock::setLastChangedParam(const Param& last_param, bool user_provided) {} - - void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptor& in_param, const char* char_name){} + BlockDescriptor::BlockDescriptor() {} + ParamDescriptor::ParamDescriptor(param_handle_t p, + merge_func_t merge_func, + deserialize_func_t deserialize_func, + serialize_func_t serialize_func, + validation_func_t validation_func, + inspect_func_t inspect_func, + S32 min_count, + S32 max_count){} + ParamDescriptor::~ParamDescriptor() {} + + void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name){} param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;} - + void BaseBlock::addSynonym(Param& param, const std::string& synonym) {} + void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) { descriptor.mCurrentBlockPtr = this; @@ -81,26 +88,23 @@ namespace LLInitParam { const U8* my_addr = reinterpret_cast<const U8*>(this); const U8* block_addr = reinterpret_cast<const U8*>(enclosing_block); - mEnclosingBlockOffset = (U16)(my_addr - block_addr); + mEnclosingBlockOffset = 0x7FFFffff & ((U32)(my_addr - block_addr)); } - bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack){ return true; } - bool BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t name_stack, const LLInitParam::BaseBlock* diff_block) const { return true; } - bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack) const { return true; } - bool BaseBlock::merge(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } + bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; } + void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {} + bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_count, S32 max_count) const { return true; } + bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } bool BaseBlock::validateBlock(bool emit_errors) const { return true; } - TypedParam<LLUIColor >::TypedParam(BlockDescriptor& descriptor, const char* name, const LLUIColor& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, name, value, func, min_count, max_count) + ParamValue<LLUIColor, TypeValues<LLUIColor> >::ParamValue(const LLUIColor& color) + : super_t(color) {} - void TypedParam<LLUIColor>::setValueFromBlock() const + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateValueFromBlock() {} - void TypedParam<LLUIColor>::setBlockFromValue() - {} - - void TypeValues<LLUIColor>::declareValues() + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateBlockFromValue(bool) {} bool ParamCompare<const LLFontGL*, false>::equals(const LLFontGL* a, const LLFontGL* b) @@ -108,14 +112,15 @@ namespace LLInitParam return false; } - TypedParam<const LLFontGL*>::TypedParam(BlockDescriptor& descriptor, const char* _name, const LLFontGL*const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) - : super_t(descriptor, _name, value, func, min_count, max_count) + + ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::ParamValue(const LLFontGL* fontp) + : super_t(fontp) {} - void TypedParam<const LLFontGL*>::setValueFromBlock() const + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateValueFromBlock() {} - void TypedParam<const LLFontGL*>::setBlockFromValue() + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateBlockFromValue(bool) {} void TypeValues<LLFontGL::HAlign>::declareValues() @@ -127,10 +132,10 @@ namespace LLInitParam void TypeValues<LLFontGL::ShadowType>::declareValues() {} - void TypedParam<LLUIImage*>::setValueFromBlock() const + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateValueFromBlock() {} - void TypedParam<LLUIImage*>::setBlockFromValue() + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateBlockFromValue(bool) {} bool ParamCompare<LLUIImage*, false>::equals( |