diff options
Diffstat (limited to 'indra/llui')
91 files changed, 4994 insertions, 2141 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 8e78a5fefd..684e393cba 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -111,6 +111,7 @@ set(llui_SOURCE_FILES llviewmodel.cpp llview.cpp llviewquery.cpp + llwindowshade.cpp ) set(llui_HEADER_FILES @@ -158,6 +159,8 @@ set(llui_HEADER_FILES llnotifications.h llnotificationslistener.h llnotificationsutil.h + llnotificationtemplate.h + llnotificationvisibilityrule.h llpanel.h llprogressbar.h llradiogroup.h @@ -208,6 +211,7 @@ set(llui_HEADER_FILES llviewmodel.h llview.h llviewquery.h + llwindowshade.h ) set_source_files_properties(${llui_HEADER_FILES} @@ -240,12 +244,12 @@ target_link_libraries(llui ${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender ) -if(LL_TESTS) - # Add tests - include(LLAddBuildTest) - SET(llui_TEST_SOURCE_FILES - llurlmatch.cpp - llurlentry.cpp - ) - LL_ADD_PROJECT_UNIT_TESTS(llui "${llui_TEST_SOURCE_FILES}") -endif(LL_TESTS) +# Add tests +if (LL_TESTS) + include(LLAddBuildTest) + SET(llui_TEST_SOURCE_FILES + llurlmatch.cpp + llurlentry.cpp + ) + LL_ADD_PROJECT_UNIT_TESTS(llui "${llui_TEST_SOURCE_FILES}") +endif (LL_TESTS)
\ No newline at end of file diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp index f9ffaaa646..d636161baf 100644 --- a/indra/llui/llaccordionctrl.cpp +++ b/indra/llui/llaccordionctrl.cpp @@ -83,7 +83,7 @@ LLAccordionCtrl::LLAccordionCtrl() : LLPanel() mSingleExpansion = false; mFitParent = false; - LLUICtrlFactory::getInstance()->buildPanel(this, "accordion_parent.xml"); + buildFromFile( "accordion_parent.xml"); } //--------------------------------------------------------------------------------- diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp index b7da5f4a1b..9e4849c58b 100644 --- a/indra/llui/llaccordionctrltab.cpp +++ b/indra/llui/llaccordionctrltab.cpp @@ -141,6 +141,7 @@ LLAccordionCtrlTab::LLAccordionCtrlTabHeader::LLAccordionCtrlTabHeader( textboxParams.use_ellipses = true; textboxParams.bg_visible = false; textboxParams.mouse_opaque = false; + textboxParams.parse_urls = false; mHeaderTextbox = LLUICtrlFactory::create<LLTextBox>(textboxParams); addChild(mHeaderTextbox); } @@ -202,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()); @@ -455,8 +457,7 @@ BOOL LLAccordionCtrlTab::handleMouseDown(S32 x, S32 y, MASK mask) { if(y >= (getRect().getHeight() - HEADER_HEIGHT) ) { - LLAccordionCtrlTabHeader* header = getChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - header->setFocus(true); + mHeader->setFocus(true); changeOpenClose(getDisplayChildren()); //reset stored state @@ -508,10 +509,9 @@ void LLAccordionCtrlTab::setAccordionView(LLView* panel) std::string LLAccordionCtrlTab::getTitle() const { - LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - if (header) + if (mHeader) { - return header->getTitle(); + return mHeader->getTitle(); } else { @@ -521,57 +521,51 @@ std::string LLAccordionCtrlTab::getTitle() const void LLAccordionCtrlTab::setTitle(const std::string& title, const std::string& hl) { - LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - if (header) + if (mHeader) { - header->setTitle(title, hl); + mHeader->setTitle(title, hl); } } void LLAccordionCtrlTab::setTitleFontStyle(std::string style) { - LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - if (header) + if (mHeader) { - header->setTitleFontStyle(style); + mHeader->setTitleFontStyle(style); } } void LLAccordionCtrlTab::setTitleColor(LLUIColor color) { - LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - if (header) + if (mHeader) { - header->setTitleColor(color); + mHeader->setTitleColor(color); } } boost::signals2::connection LLAccordionCtrlTab::setFocusReceivedCallback(const focus_signal_t::slot_type& cb) { - LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - if (header) + if (mHeader) { - return header->setFocusReceivedCallback(cb); + return mHeader->setFocusReceivedCallback(cb); } return boost::signals2::connection(); } boost::signals2::connection LLAccordionCtrlTab::setFocusLostCallback(const focus_signal_t::slot_type& cb) { - LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - if (header) + if (mHeader) { - return header->setFocusLostCallback(cb); + return mHeader->setFocusLostCallback(cb); } return boost::signals2::connection(); } void LLAccordionCtrlTab::setSelected(bool is_selected) { - LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - if (header) + if (mHeader) { - header->setSelected(is_selected); + mHeader->setSelected(is_selected); } } @@ -735,6 +729,12 @@ S32 LLAccordionCtrlTab::notifyParent(const LLSD& info) return 1; } + + if (!getDisplayChildren()) + { + // Don't pass scrolling event further if our contents are invisible (STORM-298). + return 1; + } } return LLUICtrl::notifyParent(info); @@ -769,8 +769,7 @@ S32 LLAccordionCtrlTab::notify(const LLSD& info) BOOL LLAccordionCtrlTab::handleKey(KEY key, MASK mask, BOOL called_from_parent) { - LLAccordionCtrlTabHeader* header = getChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - if( !header->hasFocus() ) + if( !mHeader->hasFocus() ) return LLUICtrl::handleKey(key, mask, called_from_parent); if ( (key == KEY_RETURN )&& mask == MASK_NONE) @@ -823,15 +822,19 @@ BOOL LLAccordionCtrlTab::handleKey(KEY key, MASK mask, BOOL called_from_parent) void LLAccordionCtrlTab::showAndFocusHeader() { - LLAccordionCtrlTabHeader* header = getChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); - header->setFocus(true); - header->setSelected(mSelectionEnabled); + mHeader->setFocus(true); + mHeader->setSelected(mSelectionEnabled); LLRect screen_rc; - LLRect selected_rc = header->getRect(); + LLRect selected_rc = mHeader->getRect(); localRectToScreen(selected_rc, &screen_rc); - notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue())); + // This call to notifyParent() is intended to deliver "scrollToShowRect" command + // to the parent LLAccordionCtrl so by calling it from the direct parent of this + // accordion tab (assuming that the parent is an LLAccordionCtrl) the calls chain + // is shortened and messages from inside the collapsed tabs are avoided. + // See STORM-536. + getParent()->notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue())); } void LLAccordionCtrlTab::storeOpenCloseState() { diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h index d6ac8cbc8f..dddaa581e6 100644 --- a/indra/llui/llaccordionctrltab.h +++ b/indra/llui/llaccordionctrltab.h @@ -85,10 +85,10 @@ public: Optional<bool> selection_enabled; - Optional<S32> padding_left; - Optional<S32> padding_right; - Optional<S32> padding_top; - Optional<S32> padding_bottom; + Optional<S32> padding_left, + padding_right, + padding_top, + padding_bottom; Params(); }; @@ -170,7 +170,7 @@ public: virtual BOOL handleScrollWheel( S32 x, S32 y, S32 clicks ); - virtual bool addChild(LLView* child, S32 tab_group); + virtual bool addChild(LLView* child, S32 tab_group = 0 ); bool isExpanded() const { return mDisplayChildren; } diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index f0f34ebd4f..45ceaff696 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -98,7 +98,8 @@ 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), + use_draw_context_alpha("use_draw_context_alpha", true) { addSynonym(is_toggle, "toggle"); held_down_delay.seconds = 0.5f; @@ -114,7 +115,6 @@ LLButton::LLButton(const LLButton::Params& p) mFlashing( FALSE ), mCurGlowStrength(0.f), mNeedsHighlight(FALSE), - mMouseOver(false), mUnselectedLabel(p.label()), mSelectedLabel(p.label_selected()), mGLFont(p.font), @@ -159,7 +159,8 @@ LLButton::LLButton(const LLButton::Params& p) mLastDrawCharsCount(0), mMouseDownSignal(NULL), mMouseUpSignal(NULL), - mHeldDownSignal(NULL) + mHeldDownSignal(NULL), + mUseDrawContextAlpha(p.use_draw_context_alpha) { static LLUICachedControl<S32> llbutton_orig_h_pad ("UIButtonOrigHPad", 0); @@ -499,11 +500,7 @@ void LLButton::onMouseEnter(S32 x, S32 y, MASK mask) LLUICtrl::onMouseEnter(x, y, mask); if (isInEnabledChain()) - { mNeedsHighlight = TRUE; - } - - mMouseOver = true; } void LLButton::onMouseLeave(S32 x, S32 y, MASK mask) @@ -511,7 +508,6 @@ void LLButton::onMouseLeave(S32 x, S32 y, MASK mask) LLUICtrl::onMouseLeave(x, y, mask); mNeedsHighlight = FALSE; - mMouseOver = true; } void LLButton::setHighlight(bool b) @@ -545,7 +541,7 @@ BOOL LLButton::handleHover(S32 x, S32 y, MASK mask) // virtual void LLButton::draw() { - F32 alpha = getDrawContext().mAlpha; + 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); @@ -564,11 +560,19 @@ void LLButton::draw() pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN)); } - // Unselected image assignments + bool mouse_pressed_and_over = false; + if (hasMouseCapture()) + { + S32 local_mouse_x ; + S32 local_mouse_y; + LLUI::getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); + mouse_pressed_and_over = pointInView(local_mouse_x, local_mouse_y); + } + bool enabled = isInEnabledChain(); bool pressed = pressed_by_keyboard - || (hasMouseCapture() && mMouseOver) + || mouse_pressed_and_over || mForcePressedState; bool selected = getToggleState(); @@ -1117,7 +1121,7 @@ void LLButton::setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) // Get the visibility control name for the floater std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) - button->setControlVariable(LLUI::sSettingGroups["floater"]->getControl(vis_control_name)); + button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); // Set the clicked callback to toggle the floater button->setClickedCallback(boost::bind(&LLFloaterReg::toggleFloaterInstance, sdname)); } @@ -1131,7 +1135,7 @@ void LLButton::setDockableFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) // Get the visibility control name for the floater std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) - button->setControlVariable(LLUI::sSettingGroups["floater"]->getControl(vis_control_name)); + button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); // Set the clicked callback to toggle the floater button->setClickedCallback(boost::bind(&LLDockableFloater::toggleInstance, sdname)); } diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index d87ceb7c42..0cfc393e05 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -27,7 +27,6 @@ #ifndef LL_LLBUTTON_H #define LL_LLBUTTON_H -#include "lluuid.h" #include "llcontrol.h" #include "lluictrl.h" #include "v4color.h" @@ -53,6 +52,8 @@ S32 round_up(S32 grid, S32 value); class LLUICtrlFactory; +class LLUIImage; +class LLUUID; // // Classes @@ -124,6 +125,8 @@ public: Optional<F32> hover_glow_amount; Optional<TimeIntervalParam> held_down_delay; + Optional<bool> use_draw_context_alpha; + Params(); }; @@ -338,6 +341,8 @@ private: S32 mImageOverlayTopPad; S32 mImageOverlayBottomPad; + bool mUseDrawContextAlpha; + /* * Space between image_overlay and label */ @@ -350,7 +355,6 @@ private: BOOL mCommitOnReturn; BOOL mFadeWhenDisabled; bool mForcePressedState; - bool mMouseOver; LLFrameTimer mFlashingTimer; }; diff --git a/indra/llui/llcheckboxctrl.cpp b/indra/llui/llcheckboxctrl.cpp index cbc8f12472..4fe444c1a4 100644 --- a/indra/llui/llcheckboxctrl.cpp +++ b/indra/llui/llcheckboxctrl.cpp @@ -50,9 +50,7 @@ template class LLCheckBoxCtrl* LLView::getChild<class LLCheckBoxCtrl>( const std::string& name, BOOL recurse) const; LLCheckBoxCtrl::Params::Params() -: text_enabled_color("text_enabled_color"), - text_disabled_color("text_disabled_color"), - initial_value("initial_value", false), +: initial_value("initial_value", false), label_text("label_text"), check_button("check_button"), radio_style("radio_style") @@ -61,8 +59,8 @@ LLCheckBoxCtrl::Params::Params() LLCheckBoxCtrl::LLCheckBoxCtrl(const LLCheckBoxCtrl::Params& p) : LLUICtrl(p), - mTextEnabledColor(p.text_enabled_color()), - mTextDisabledColor(p.text_disabled_color()), + mTextEnabledColor(p.label_text.text_color()), + mTextDisabledColor(p.label_text.text_readonly_color()), mFont(p.font()) { mViewModel->setValue(LLSD(p.initial_value)); @@ -89,29 +87,20 @@ LLCheckBoxCtrl::LLCheckBoxCtrl(const LLCheckBoxCtrl::Params& p) { tbparams.font(p.font); } - tbparams.text_color( p.enabled() ? p.text_enabled_color() : p.text_disabled_color() ); 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; @@ -177,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/llcheckboxctrl.h b/indra/llui/llcheckboxctrl.h index 0147088280..67d8091a97 100644 --- a/indra/llui/llcheckboxctrl.h +++ b/indra/llui/llcheckboxctrl.h @@ -52,8 +52,6 @@ public: struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { - Optional<LLUIColor> text_enabled_color; - Optional<LLUIColor> text_disabled_color; Optional<bool> initial_value; // override LLUICtrl initial_value Optional<LLTextBox::Params> label_text; diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 910bab9a97..a4d1854bc8 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -52,8 +52,6 @@ #include "lltooltip.h" // Globals -S32 LLCOMBOBOX_HEIGHT = 0; -S32 LLCOMBOBOX_WIDTH = 0; S32 MAX_COMBO_WIDTH = 500; static LLDefaultChildRegistry::Register<LLComboBox> register_combo_box("combo_box"); @@ -96,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) @@ -139,8 +138,8 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p) // Grab the mouse-up event and make sure the button state is correct mList->setMouseUpCallback(boost::bind(&LLComboBox::onListMouseUp, this)); - for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items().begin(); - it != p.items().end(); + for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items.begin(); + it != p.items.end(); ++it) { LLScrollListItem::Params item_params = *it; @@ -232,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) @@ -317,7 +320,7 @@ void LLComboBox::setValue(const LLSD& value) LLScrollListItem* item = mList->getFirstSelected(); if (item) { - setLabel(getSelectedItemLabel()); + updateLabel(); } mLastSelectedIndex = mList->getFirstSelectedIndex(); } @@ -385,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) { @@ -486,7 +506,7 @@ void LLComboBox::createLineEditor(const LLComboBox::Params& p) LLLineEditor::Params params = p.combo_editor; params.rect(text_entry_rect); params.default_text(LLStringUtil::null); - params.max_length_bytes(mMaxChars); + params.max_length.bytes(mMaxChars); params.commit_callback.function(boost::bind(&LLComboBox::onTextCommit, this, _2)); params.keystroke_callback(boost::bind(&LLComboBox::onTextEntry, this, _1)); params.commit_on_focus_lost(false); @@ -702,7 +722,7 @@ void LLComboBox::onItemSelected(const LLSD& data) mLastSelectedIndex = getCurrentIndex(); if (mLastSelectedIndex != -1) { - setLabel(getSelectedItemLabel()); + updateLabel(); if (mAllowTextEntry) { @@ -771,7 +791,8 @@ 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->isEmpty()) { showList(); } @@ -835,6 +856,10 @@ void LLComboBox::onTextEntry(LLLineEditor* line_editor) mList->deselectAllItems(); mLastSelectedIndex = -1; } + if (mTextChangedCallback != NULL) + { + (mTextChangedCallback)(line_editor, LLSD()); + } return; } @@ -879,6 +904,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 f369147ded..64dbaea306 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -43,9 +43,6 @@ class LLFontGL; class LLViewBorder; -extern S32 LLCOMBOBOX_HEIGHT; -extern S32 LLCOMBOBOX_WIDTH; - class LLComboBox : public LLUICtrl, public LLCtrlListInterface { @@ -76,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; @@ -136,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 @@ -150,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 ); @@ -193,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); @@ -223,9 +226,10 @@ 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; + boost::signals2::connection mTopLostSignalConnection; + S32 mLastSelectedIndex; }; // A combo box with icons for the list of items. diff --git a/indra/llui/lldockcontrol.cpp b/indra/llui/lldockcontrol.cpp index d48674f306..b1c27126d9 100644 --- a/indra/llui/lldockcontrol.cpp +++ b/indra/llui/lldockcontrol.cpp @@ -160,8 +160,10 @@ bool LLDockControl::isDockVisible() case TOP: { // check is dock inside parent rect + // assume that parent for all dockable flaoters + // is the root view LLRect dockParentRect = - mDockWidget->getParent()->calcScreenRect(); + mDockWidget->getRootView()->calcScreenRect(); if (dockRect.mRight <= dockParentRect.mLeft || dockRect.mLeft >= dockParentRect.mRight) { @@ -220,10 +222,15 @@ void LLDockControl::moveDockable() 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 +264,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,9 +299,21 @@ 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); 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 e1203971ea..d19e33ea55 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -1,4 +1,5 @@ /** + * @file llfloater.cpp * @brief LLFloater base class * @@ -36,6 +37,7 @@ #include "lluictrlfactory.h" #include "llbutton.h" #include "llcheckboxctrl.h" +#include "lldir.h" #include "lldraghandle.h" #include "llfloaterreg.h" #include "llfocusmgr.h" @@ -60,7 +62,6 @@ // use this to control "jumping" behavior when Ctrl-Tabbing const S32 TABBED_FLOATER_OFFSET = 0; - std::string LLFloater::sButtonNames[BUTTON_COUNT] = { "llfloater_close_btn", //BUTTON_CLOSE @@ -199,6 +200,21 @@ void LLFloater::initClass() { sButtonToolTips[i] = LLTrans::getString( sButtonToolTipsIndex[i] ); } + + LLControlVariable* ctrl = LLUI::sSettingGroups["config"]->getControl("ActiveFloaterTransparency").get(); + if (ctrl) + { + ctrl->getSignal()->connect(boost::bind(&LLFloater::updateActiveFloaterTransparency)); + updateActiveFloaterTransparency(); + } + + ctrl = LLUI::sSettingGroups["config"]->getControl("InactiveFloaterTransparency").get(); + if (ctrl) + { + ctrl->getSignal()->connect(boost::bind(&LLFloater::updateInactiveFloaterTransparency)); + updateInactiveFloaterTransparency(); + } + } // defaults for floater param block pulled from widgets/floater.xml @@ -206,7 +222,7 @@ 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), @@ -231,7 +247,8 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) mTornOff(false), mHasBeenDraggedWhileMinimized(FALSE), mPreviousMinimizedBottom(0), - mPreviousMinimizedLeft(0) + mPreviousMinimizedLeft(0), + mMinimizeSignal(NULL) // mNotificationContext(NULL) { mHandle.bind(this); @@ -255,9 +272,6 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) initFromParams(p); - // chrome floaters don't take focus at all - setFocusRoot(!getIsChrome()); - initFloater(p); } @@ -345,6 +359,18 @@ void LLFloater::layoutDragHandle() updateTitleButtons(); } +// static +void LLFloater::updateActiveFloaterTransparency() +{ + sActiveControlTransparency = LLUI::sSettingGroups["config"]->getF32("ActiveFloaterTransparency"); +} + +// static +void LLFloater::updateInactiveFloaterTransparency() +{ + sInactiveControlTransparency = LLUI::sSettingGroups["config"]->getF32("InactiveFloaterTransparency"); +} + void LLFloater::addResizeCtrls() { // Resize bars (sides) @@ -493,13 +519,15 @@ LLFloater::~LLFloater() setVisible(false); // We're not visible if we're destroyed storeVisibilityControl(); storeDockStateControl(); + + delete mMinimizeSignal; } void LLFloater::storeRectControl() { if( mRectControl.size() > 1 ) { - LLUI::sSettingGroups["floater"]->setRect( mRectControl, getRect() ); + getControlGroup()->setRect( mRectControl, getRect() ); } } @@ -507,7 +535,7 @@ void LLFloater::storeVisibilityControl() { if( !sQuitting && mVisibilityControl.size() > 1 ) { - LLUI::sSettingGroups["floater"]->setBOOL( mVisibilityControl, getVisible() ); + getControlGroup()->setBOOL( mVisibilityControl, getVisible() ); } } @@ -515,10 +543,47 @@ void LLFloater::storeDockStateControl() { if( !sQuitting && mDocStateControl.size() > 1 ) { - LLUI::sSettingGroups["floater"]->setBOOL( mDocStateControl, isDocked() ); + getControlGroup()->setBOOL( mDocStateControl, isDocked() ); + } +} + +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) +{ + std::string ctrl_name = name; + + // Add the key to the control name if appropriate. + if (key.isString() && !key.asString().empty()) + { + ctrl_name += "_" + key.asString(); } + + return ctrl_name; } +// static +LLControlGroup* LLFloater::getControlGroup() +{ + // Floater size, position, visibility, etc are saved in per-account settings. + return LLUI::sSettingGroups["account"]; +} void LLFloater::setVisible( BOOL visible ) { @@ -775,7 +840,7 @@ void LLFloater::applyRectControl() // override center if we have saved rect control if (mRectControl.size() > 1) { - const LLRect& rect = LLUI::sSettingGroups["floater"]->getRect(mRectControl); + const LLRect& rect = getControlGroup()->getRect(mRectControl); if (rect.getWidth() > 0 && rect.getHeight() > 0) { translate( rect.mLeft - getRect().mLeft, rect.mBottom - getRect().mBottom); @@ -791,7 +856,7 @@ void LLFloater::applyDockState() { if (mDocStateControl.size() > 1) { - bool dockState = LLUI::sSettingGroups["floater"]->getBOOL(mDocStateControl); + bool dockState = getControlGroup()->getBOOL(mDocStateControl); setDocked(dockState); } @@ -960,6 +1025,11 @@ void LLFloater::setMinimized(BOOL minimize) if (minimize == mMinimized) return; + if(mMinimizeSignal) + { + (*mMinimizeSignal)(this, LLSD(minimize)); + } + if (minimize) { // minimized flag should be turned on before release focus @@ -1117,6 +1187,7 @@ void LLFloater::setFocus( BOOL b ) last_focus->setFocus(TRUE); } } + updateTransparency(b ? TT_ACTIVE : TT_INACTIVE); } // virtual @@ -1389,6 +1460,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); } } @@ -1576,7 +1650,8 @@ void LLFloater::onClickCloseBtn() // virtual void LLFloater::draw() { - F32 alpha = getDrawContext().mAlpha; + const F32 alpha = getCurrentTransparency(); + // draw background if( isBackgroundVisible() ) { @@ -1674,7 +1749,6 @@ void LLFloater::draw() void LLFloater::drawShadow(LLPanel* panel) { - F32 alpha = panel->getDrawContext().mAlpha; S32 left = LLPANEL_BORDER_WIDTH; S32 top = panel->getRect().getHeight() - LLPANEL_BORDER_WIDTH; S32 right = panel->getRect().getWidth() - LLPANEL_BORDER_WIDTH; @@ -1691,10 +1765,32 @@ void LLFloater::drawShadow(LLPanel* panel) shadow_color.mV[VALPHA] *= 0.5f; } gl_drop_shadow(left, top, right, bottom, - shadow_color % alpha, + 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, @@ -1954,6 +2050,7 @@ LLFloaterView::LLFloaterView (const Params& p) : LLUICtrl (p), mFocusCycleMode(FALSE), + mMinimizePositionVOffset(0), mSnapOffsetBottom(0), mSnapOffsetRight(0) { @@ -2352,7 +2449,9 @@ void LLFloaterView::closeAllChildren(bool app_quitting) // Attempt to close floater. This will cause the "do you want to save" // dialogs to appear. - if (floaterp->canClose() && !floaterp->isDead()) + // Skip invisible floaters if we're not quitting (STORM-192). + if (floaterp->canClose() && !floaterp->isDead() && + (app_quitting || floaterp->getVisible())) { floaterp->closeFloater(app_quitting); } @@ -2491,9 +2590,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; } @@ -2664,18 +2767,20 @@ void LLFloater::setInstanceName(const std::string& name) mInstanceName = name; if (!mInstanceName.empty()) { + std::string ctrl_name = getControlName(mInstanceName, mKey); + // save_rect and save_visibility only apply to registered floaters if (!mRectControl.empty()) { - mRectControl = LLFloaterReg::declareRectControl(mInstanceName); + mRectControl = LLFloaterReg::declareRectControl(ctrl_name); } if (!mVisibilityControl.empty()) { - mVisibilityControl = LLFloaterReg::declareVisibilityControl(mInstanceName); + mVisibilityControl = LLFloaterReg::declareVisibilityControl(ctrl_name); } if(!mDocStateControl.empty()) { - mDocStateControl = LLFloaterReg::declareDockStateControl(mInstanceName); + mDocStateControl = LLFloaterReg::declareDockStateControl(ctrl_name); } } @@ -2764,16 +2869,28 @@ void LLFloater::initFromParams(const LLFloater::Params& p) // close callback if (p.close_callback.isProvided()) { - mCloseSignal.connect(initCommitCallback(p.close_callback)); + setCloseCallback(initCommitCallback(p.close_callback)); } } +boost::signals2::connection LLFloater::setMinimizeCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMinimizeSignal) mMinimizeSignal = new commit_signal_t(); + return mMinimizeSignal->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"); bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::string& filename, LLXMLNodePtr output_node) { Params params(LLUICtrlFactory::getDefaultParams<LLFloater>()); - LLXUIParser::instance().readXUI(node, params, filename); // *TODO: Error checking + LLXUIParser parser; + parser.readXUI(node, params, filename); // *TODO: Error checking if (output_node) { @@ -2781,8 +2898,7 @@ bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::str setupParamsForExport(output_params, parent); Params default_params(LLUICtrlFactory::getDefaultParams<LLFloater>()); output_node->setName(node->getName()->mString); - LLXUIParser::instance().writeXUI( - output_node, output_params, &default_params); + parser.writeXUI(output_node, output_params, &default_params); } // Default floater position to top-left corner of screen @@ -2800,7 +2916,7 @@ bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::str params.from_xui = true; applyXUILayout(params, parent); initFromParams(params); - + initFloater(params); LLMultiFloater* last_host = LLFloater::getFloaterHost(); @@ -2877,3 +2993,64 @@ bool LLFloater::isVisible(const LLFloater* floater) { return floater && floater->getVisible(); } + +static LLFastTimer::DeclareTimer FTM_BUILD_FLOATERS("Build Floaters"); + +bool LLFloater::buildFromFile(const std::string& filename, LLXMLNodePtr output_node) +{ + LLFastTimer timer(FTM_BUILD_FLOATERS); + LLXMLNodePtr root; + + //if exporting, only load the language being exported, + //instead of layering localized version on top of english + if (output_node) + { + if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root)) + { + llwarns << "Couldn't parse floater from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; + return false; + } + } + else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + llwarns << "Couldn't parse floater from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; + return false; + } + + // root must be called floater + if( !(root->hasName("floater") || root->hasName("multi_floater")) ) + { + llwarns << "Root node should be named floater in: " << filename << llendl; + return false; + } + + bool res = true; + + lldebugs << "Building floater " << filename << llendl; + LLUICtrlFactory::instance().pushFileName(filename); + { + if (!getFactoryMap().empty()) + { + LLPanel::sFactoryStack.push_front(&getFactoryMap()); + } + + // for local registry callbacks; define in constructor, referenced in XUI or postBuild + getCommitCallbackRegistrar().pushScope(); + getEnableCallbackRegistrar().pushScope(); + + res = initFloaterXML(root, getParent(), filename, output_node); + + setXMLFilename(filename); + + getCommitCallbackRegistrar().popScope(); + getEnableCallbackRegistrar().popScope(); + + if (!getFactoryMap().empty()) + { + LLPanel::sFactoryStack.pop_front(); + } + } + LLUICtrlFactory::instance().popFileName(); + + return res; +} diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 5e482cbac3..5b7b020881 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -141,6 +141,10 @@ public: // Don't export top/left for rect, only height/width static void setupParamsForExport(Params& p, LLView* parent); + bool buildFromFile(const std::string &filename, LLXMLNodePtr output_node = NULL); + + boost::signals2::connection setMinimizeCallback( 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); @@ -203,6 +207,11 @@ public: 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(); bool isMinimizeable() const{ return mCanMinimize; } bool isCloseable() const{ return mCanClose; } @@ -276,6 +285,8 @@ public: static void setFloaterHost(LLMultiFloater* hostp) {sHostp = hostp; } static LLMultiFloater* getFloaterHost() {return sHostp; } + + void updateTransparency(ETypeTransparency transparency_type); protected: @@ -333,6 +344,10 @@ private: 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 // Public so external views or floaters can watch for this floater opening @@ -342,6 +357,8 @@ public: // Public so external views or floaters can watch for this floater closing commit_signal_t mCloseSignal; + commit_signal_t* mMinimizeSignal; + protected: std::string mRectControl; std::string mVisibilityControl; @@ -479,10 +496,10 @@ 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: + LLHandle<LLView> mSnapView; BOOL mFocusCycleMode; S32 mSnapOffsetBottom; S32 mSnapOffsetRight; diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index ccffe98c96..4677d535db 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -121,7 +121,7 @@ LLFloater* LLFloaterReg::getInstance(const std::string& name, const LLSD& key) res = build_func(key); - bool success = LLUICtrlFactory::getInstance()->buildFloater(res, xui_file, NULL); + bool success = res->buildFromFile(xui_file, NULL); if (!success) { llwarns << "Failed to build floater type: '" << name << "'." << llendl; @@ -284,9 +284,9 @@ void LLFloaterReg::showInitialVisibleInstances() { const std::string& name = iter->first; std::string controlname = getVisibilityControlName(name); - if (LLUI::sSettingGroups["floater"]->controlExists(controlname)) + if (LLFloater::getControlGroup()->controlExists(controlname)) { - BOOL isvis = LLUI::sSettingGroups["floater"]->getBOOL(controlname); + BOOL isvis = LLFloater::getControlGroup()->getBOOL(controlname); if (isvis) { showInstance(name, LLSD()); // keyed floaters shouldn't set save_vis to true @@ -340,7 +340,7 @@ std::string LLFloaterReg::getRectControlName(const std::string& name) std::string LLFloaterReg::declareRectControl(const std::string& name) { std::string controlname = getRectControlName(name); - LLUI::sSettingGroups["floater"]->declareRect(controlname, LLRect(), + LLFloater::getControlGroup()->declareRect(controlname, LLRect(), llformat("Window Position and Size for %s", name.c_str()), TRUE); return controlname; @@ -358,7 +358,7 @@ std::string LLFloaterReg::getVisibilityControlName(const std::string& name) std::string LLFloaterReg::declareVisibilityControl(const std::string& name) { std::string controlname = getVisibilityControlName(name); - LLUI::sSettingGroups["floater"]->declareBOOL(controlname, FALSE, + LLFloater::getControlGroup()->declareBOOL(controlname, FALSE, llformat("Window Visibility for %s", name.c_str()), TRUE); return controlname; @@ -368,7 +368,7 @@ std::string LLFloaterReg::declareVisibilityControl(const std::string& name) std::string LLFloaterReg::declareDockStateControl(const std::string& name) { std::string controlname = getDockStateControlName(name); - LLUI::sSettingGroups["floater"]->declareBOOL(controlname, TRUE, + LLFloater::getControlGroup()->declareBOOL(controlname, TRUE, llformat("Window Docking state for %s", name.c_str()), TRUE); return controlname; @@ -391,11 +391,11 @@ void LLFloaterReg::registerControlVariables() for (build_map_t::iterator iter = sBuildMap.begin(); iter != sBuildMap.end(); ++iter) { const std::string& name = iter->first; - if (LLUI::sSettingGroups["floater"]->controlExists(getRectControlName(name))) + if (LLFloater::getControlGroup()->controlExists(getRectControlName(name))) { declareRectControl(name); } - if (LLUI::sSettingGroups["floater"]->controlExists(getVisibilityControlName(name))) + if (LLFloater::getControlGroup()->controlExists(getVisibilityControlName(name))) { declareVisibilityControl(name); } @@ -419,7 +419,7 @@ void LLFloaterReg::initUICtrlToFloaterVisibilityControl(LLUICtrl* ctrl, const LL // 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(LLUI::sSettingGroups["floater"]->getControl(vis_control_name)); + ctrl->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); } // callback args may use "floatername.key" format diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h index 94387fb41a..8414b92113 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; 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/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index 1f16d12add..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>(); } } @@ -315,6 +332,20 @@ void LLFocusMgr::removeKeyboardFocusWithoutCallback( const LLFocusableElement* f } } +bool LLFocusMgr::keyboardFocusHasAccelerators() const +{ + LLView* focus_view = dynamic_cast<LLView*>(mKeyboardFocus); + while( focus_view ) + { + if(focus_view->hasAccelerators()) + { + return true; + } + + focus_view = focus_view->getParent(); + } + return false; +} void LLFocusMgr::setMouseCapture( LLMouseHandler* new_captor ) { @@ -442,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()); @@ -456,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 eef82a3b5a..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. @@ -118,6 +118,10 @@ public: void unlockFocus(); BOOL focusLocked() const { return mLockedView != NULL; } + bool keyboardFocusHasAccelerators() const; + + struct Impl; + private: LLUICtrl* mLockedView; @@ -130,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; @@ -141,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/llhandle.h b/indra/llui/llhandle.h index a43f095d67..8c000eee48 100644 --- a/indra/llui/llhandle.h +++ b/indra/llui/llhandle.h @@ -61,13 +61,6 @@ public: return *this; } - template<typename Subclass> - LLHandle<T>& operator =(const LLHandle<Subclass>& other) - { - mTombStone = other.mTombStone; - return *this; - } - bool isDead() const { return mTombStone->getTarget() == NULL; @@ -99,7 +92,6 @@ public: { return lhs.mTombStone > rhs.mTombStone; } -protected: protected: LLPointer<LLTombStone<T> > mTombStone; diff --git a/indra/llui/lliconctrl.cpp b/indra/llui/lliconctrl.cpp index 627957061d..47f2cfaf89 100644 --- a/indra/llui/lliconctrl.cpp +++ b/indra/llui/lliconctrl.cpp @@ -41,6 +41,7 @@ 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; @@ -51,6 +52,7 @@ 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 +73,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/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp index 5cc9add1e2..9b6830a816 100644 --- a/indra/llui/lllayoutstack.cpp +++ b/indra/llui/lllayoutstack.cpp @@ -35,97 +35,76 @@ #include "llresizebar.h" #include "llcriticaldamp.h" -static LLDefaultChildRegistry::Register<LLLayoutStack> register_layout_stack("layout_stack", &LLLayoutStack::fromXML); +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); +} // -// LLLayoutStack +// LLLayoutPanel // -struct LLLayoutStack::LayoutPanel +LLLayoutPanel::LLLayoutPanel(const Params& p) +: LLPanel(p), + 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) { - LayoutPanel(LLPanel* panelp, ELayoutOrientation orientation, S32 min_width, S32 min_height, S32 max_width, S32 max_height, BOOL auto_resize, BOOL user_resize) : mPanel(panelp), - mMinWidth(min_width), - mMinHeight(min_height), - mMaxWidth(max_width), - mMaxHeight(max_height), - mAutoResize(auto_resize), - mUserResize(user_resize), - mOrientation(orientation), - mCollapsed(FALSE), - mCollapseAmt(0.f), - mVisibleAmt(1.f), // default to fully visible - mResizeBar(NULL) + // panels initialized as hidden should not start out partially visible + if (!getVisible()) { - LLResizeBar::Side side = (orientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM; - LLRect resize_bar_rect = panelp->getRect(); - - S32 min_dim; - if (orientation == HORIZONTAL) - { - min_dim = mMinHeight; - } - else - { - min_dim = mMinWidth; - } - LLResizeBar::Params p; - p.name("resize"); - p.resizing_view(mPanel); - p.min_size(min_dim); - p.side(side); - p.snapping_enabled(false); - mResizeBar = LLUICtrlFactory::create<LLResizeBar>(p); - // panels initialized as hidden should not start out partially visible - if (!mPanel->getVisible()) - { - mVisibleAmt = 0.f; - } + mVisibleAmt = 0.f; } +} - ~LayoutPanel() - { - // probably not necessary, but... - delete mResizeBar; - mResizeBar = NULL; - } +void LLLayoutPanel::initFromParams(const Params& p) +{ + LLPanel::initFromParams(p); + setFollowsNone(); +} + + +LLLayoutPanel::~LLLayoutPanel() +{ + // probably not necessary, but... + delete mResizeBar; + mResizeBar = NULL; +} - F32 getCollapseFactor() +F32 LLLayoutPanel::getCollapseFactor(LLLayoutStack::ELayoutOrientation orientation) +{ + if (orientation == LLLayoutStack::HORIZONTAL) { - if (mOrientation == HORIZONTAL) - { - F32 collapse_amt = - clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinWidth / (F32)llmax(1, mPanel->getRect().getWidth())); - return mVisibleAmt * collapse_amt; - } - else + F32 collapse_amt = + clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinDim / (F32)llmax(1, getRect().getWidth())); + return mVisibleAmt * collapse_amt; + } + else { F32 collapse_amt = - clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, llmin(1.f, (F32)mMinHeight / (F32)llmax(1, mPanel->getRect().getHeight()))); + clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, llmin(1.f, (F32)mMinDim / (F32)llmax(1, getRect().getHeight()))); return mVisibleAmt * collapse_amt; - } } +} - LLPanel* mPanel; - S32 mMinWidth; - S32 mMinHeight; - - // mMaxWidth & mMaxHeight are added to make configurable max width of the nearby chat bar. EXT-5589 - // they are not processed by LLLayoutStack but they can be if necessary - S32 mMaxWidth; - S32 mMaxHeight; - BOOL mAutoResize; - BOOL mUserResize; - BOOL mCollapsed; - LLResizeBar* mResizeBar; - ELayoutOrientation mOrientation; - F32 mVisibleAmt; - F32 mCollapseAmt; -}; +// +// LLLayoutStack +// LLLayoutStack::Params::Params() -: orientation("orientation", std::string("vertical")), +: orientation("orientation"), animate("animate", true), clip("clip", true), + open_time_constant("open_time_constant", 0.02f), + close_time_constant("close_time_constant", 0.03f), border_size("border_size", LLCachedControl<S32>(*LLUI::sSettingGroups["config"], "UIResizeBarHeight", 0)) { name="stack"; @@ -136,10 +115,12 @@ LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& 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) + mClip(p.clip), + mOpenTimeConstant(p.open_time_constant), + mCloseTimeConstant(p.close_time_constant) {} LLLayoutStack::~LLLayoutStack() @@ -157,18 +138,18 @@ void LLLayoutStack::draw() for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) { // clip to layout rectangle, not bounding rectangle - LLRect clip_rect = (*panel_it)->mPanel->getRect(); + LLRect clip_rect = (*panel_it)->getRect(); // scale clipping rectangle by visible amount if (mOrientation == HORIZONTAL) { - clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->getCollapseFactor()); + clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->getCollapseFactor(mOrientation)); } else { - clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->getCollapseFactor()); + clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->getCollapseFactor(mOrientation)); } - LLPanel* panelp = (*panel_it)->mPanel; + LLPanel* panelp = (*panel_it); LLLocalClipRect clip(clip_rect, mClip); // only force drawing invisible children if visible amount is non-zero @@ -179,7 +160,7 @@ void LLLayoutStack::draw() void LLLayoutStack::removeChild(LLView* view) { - LayoutPanel* embedded_panelp = findEmbeddedPanel(dynamic_cast<LLPanel*>(view)); + LLLayoutPanel* embedded_panelp = findEmbeddedPanel(dynamic_cast<LLPanel*>(view)); if (embedded_panelp) { @@ -200,149 +181,16 @@ BOOL LLLayoutStack::postBuild() return TRUE; } -static void get_attribute_s32_and_write(LLXMLNodePtr node, - const char* name, - S32 *value, - S32 default_value, - LLXMLNodePtr output_child) -{ - BOOL has_attr = node->getAttributeS32(name, *value); - if (has_attr && *value != default_value && output_child) - { - // create an attribute child node - LLXMLNodePtr child_attr = output_child->createChild(name, TRUE); - child_attr->setIntValue(*value); - } -} - -static void get_attribute_bool_and_write(LLXMLNodePtr node, - const char* name, - BOOL *value, - BOOL default_value, - LLXMLNodePtr output_child) -{ - BOOL has_attr = node->getAttributeBOOL(name, *value); - if (has_attr && *value != default_value && output_child) - { - LLXMLNodePtr child_attr = output_child->createChild(name, TRUE); - child_attr->setBoolValue(*value); - } -} -//static -LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node) -{ - LLLayoutStack::Params p(LLUICtrlFactory::getDefaultParams<LLLayoutStack>()); - LLXUIParser::instance().readXUI(node, p, LLUICtrlFactory::getInstance()->getCurFileName()); - - // Export must happen before setupParams() mungles rectangles and before - // this item gets added to parent (otherwise screws up last_child_rect - // logic). JC - if (output_node) - { - Params output_params(p); - setupParamsForExport(output_params, parent); - LLLayoutStack::Params default_params(LLUICtrlFactory::getDefaultParams<LLLayoutStack>()); - output_node->setName(node->getName()->mString); - LLXUIParser::instance().writeXUI( - output_node, output_params, &default_params); - } - - p.from_xui = true; - applyXUILayout(p, parent); - LLLayoutStack* layout_stackp = LLUICtrlFactory::create<LLLayoutStack>(p); - - if (parent && layout_stackp) - { - S32 tab_group = p.tab_group.isProvided() ? p.tab_group() : parent->getLastTabGroup(); - - parent->addChild(layout_stackp, tab_group); - } - - for (LLXMLNodePtr child_node = node->getFirstChild(); child_node.notNull(); child_node = child_node->getNextSibling()) - { - const S32 DEFAULT_MIN_WIDTH = 0; - const S32 DEFAULT_MIN_HEIGHT = 0; - const S32 DEFAULT_MAX_WIDTH = S32_MAX; - const S32 DEFAULT_MAX_HEIGHT = S32_MAX; - const BOOL DEFAULT_AUTO_RESIZE = TRUE; - - S32 min_width = DEFAULT_MIN_WIDTH; - S32 min_height = DEFAULT_MIN_HEIGHT; - S32 max_width = DEFAULT_MAX_WIDTH; - S32 max_height = DEFAULT_MAX_HEIGHT; - BOOL auto_resize = DEFAULT_AUTO_RESIZE; - - LLXMLNodePtr output_child; - if (output_node) +bool LLLayoutStack::addChild(LLView* child, S32 tab_group) { - output_child = output_node->createChild("", FALSE); - } - - // Layout stack allows child nodes to acquire additional attributes, - // such as "min_width" in: <button label="Foo" min_width="100"/> - // If these attributes exist and have non-default values, write them - // to the output node. - get_attribute_s32_and_write(child_node, "min_width", &min_width, - DEFAULT_MIN_WIDTH, output_child); - get_attribute_s32_and_write(child_node, "min_height", &min_height, - DEFAULT_MIN_HEIGHT, output_child); - get_attribute_s32_and_write(child_node, "max_width", &max_width, - DEFAULT_MAX_WIDTH, output_child); - get_attribute_s32_and_write(child_node, "max_height", &max_height, - DEFAULT_MAX_HEIGHT, output_child); - get_attribute_bool_and_write(child_node, "auto_resize", &auto_resize, - DEFAULT_AUTO_RESIZE, output_child); - - if (child_node->hasName("layout_panel")) - { - BOOL user_resize = TRUE; - get_attribute_bool_and_write(child_node, "user_resize", &user_resize, - TRUE, output_child); - LLPanel* panelp = (LLPanel*)LLPanel::fromXML(child_node, layout_stackp, output_child); + LLLayoutPanel* panelp = dynamic_cast<LLLayoutPanel*>(child); if (panelp) { - panelp->setFollowsNone(); - layout_stackp->addPanel(panelp, min_width, min_height, max_width, max_height, auto_resize, user_resize); + mPanels.push_back(panelp); } + return LLView::addChild(child, tab_group); } - else - { - BOOL user_resize = FALSE; - get_attribute_bool_and_write(child_node, "user_resize", &user_resize, - FALSE, output_child); - - LLPanel::Params p; - p.mouse_opaque(false); - LLPanel* panelp = LLUICtrlFactory::create<LLPanel>(p); - LLView* new_child = LLUICtrlFactory::getInstance()->createFromXML(child_node, panelp, LLStringUtil::null, LLPanel::child_registry_t::instance(), output_child); - if (new_child) - { - // put child in new embedded panel - layout_stackp->addPanel(panelp, min_width, min_height, max_width, max_height, auto_resize, user_resize); - // resize panel to contain widget and move widget to be contained in panel - panelp->setRect(new_child->getRect()); - new_child->setOrigin(0, 0); - } - else - { - panelp->die(); - } - } - - if (output_child && !output_child->mChildren && output_child->mAttributes.empty() && output_child->getValue().empty()) - { - output_node->deleteChild(output_child); - } - } - - if (!layout_stackp->postBuild()) - { - delete layout_stackp; - return NULL; - } - return layout_stackp; -} S32 LLLayoutStack::getDefaultHeight(S32 cur_height) { @@ -368,34 +216,30 @@ S32 LLLayoutStack::getDefaultWidth(S32 cur_width) return cur_width; } -void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, S32 max_width, S32 max_height, BOOL auto_resize, BOOL user_resize, EAnimate animate, S32 index) +void LLLayoutStack::movePanel(LLPanel* panel_to_move, LLPanel* target_panel, bool move_to_front) { - // panel starts off invisible (collapsed) - if (animate == ANIMATE) - { - panel->setVisible(FALSE); - } - LayoutPanel* embedded_panel = new LayoutPanel(panel, mOrientation, min_width, min_height, max_width, max_height, auto_resize, user_resize); - - mPanels.insert(mPanels.begin() + llclamp(index, 0, (S32)mPanels.size()), embedded_panel); - - if (panel->getParent() != this) - { - addChild(panel); - } - addChild(embedded_panel->mResizeBar); + LLLayoutPanel* embedded_panel_to_move = findEmbeddedPanel(panel_to_move); + LLLayoutPanel* embedded_target_panel = move_to_front ? *mPanels.begin() : findEmbeddedPanel(target_panel); - // 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) + if (!embedded_panel_to_move || !embedded_target_panel || embedded_panel_to_move == embedded_target_panel) { - LLResizeBar* resize_barp = (*panel_it)->mResizeBar; - sendChildToFront(resize_barp); + 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); +} - // start expanding panel animation +void LLLayoutStack::addPanel(LLLayoutPanel* panel, EAnimate animate) +{ + addChild(panel); + + // panel starts off invisible (collapsed) if (animate == ANIMATE) { + panel->mVisibleAmt = 0.f; panel->setVisible(TRUE); } } @@ -407,7 +251,7 @@ void LLLayoutStack::removePanel(LLPanel* panel) void LLLayoutStack::collapsePanel(LLPanel* panel, BOOL collapsed) { - LayoutPanel* panel_container = findEmbeddedPanel(panel); + LLLayoutPanel* panel_container = findEmbeddedPanel(panel); if (!panel_container) return; panel_container->mCollapsed = collapsed; @@ -415,7 +259,7 @@ void LLLayoutStack::collapsePanel(LLPanel* panel, BOOL collapsed) void LLLayoutStack::updatePanelAutoResize(const std::string& panel_name, BOOL auto_resize) { - LayoutPanel* panel = findEmbeddedPanelByName(panel_name); + LLLayoutPanel* panel = findEmbeddedPanelByName(panel_name); if (panel) { @@ -425,7 +269,7 @@ void LLLayoutStack::updatePanelAutoResize(const std::string& panel_name, BOOL au void LLLayoutStack::setPanelUserResize(const std::string& panel_name, BOOL user_resize) { - LayoutPanel* panel = findEmbeddedPanelByName(panel_name); + LLLayoutPanel* panel = findEmbeddedPanelByName(panel_name); if (panel) { @@ -433,27 +277,25 @@ void LLLayoutStack::setPanelUserResize(const std::string& panel_name, BOOL user_ } } -bool LLLayoutStack::getPanelMinSize(const std::string& panel_name, S32* min_widthp, S32* min_heightp) +bool LLLayoutStack::getPanelMinSize(const std::string& panel_name, S32* min_dimp) { - LayoutPanel* panel = findEmbeddedPanelByName(panel_name); + LLLayoutPanel* panel = findEmbeddedPanelByName(panel_name); if (panel) { - if (min_widthp) *min_widthp = panel->mMinWidth; - if (min_heightp) *min_heightp = panel->mMinHeight; + if (min_dimp) *min_dimp = panel->mMinDim; } return NULL != panel; } -bool LLLayoutStack::getPanelMaxSize(const std::string& panel_name, S32* max_widthp, S32* max_heightp) +bool LLLayoutStack::getPanelMaxSize(const std::string& panel_name, S32* max_dimp) { - LayoutPanel* panel = findEmbeddedPanelByName(panel_name); + LLLayoutPanel* panel = findEmbeddedPanelByName(panel_name); if (panel) { - if (max_widthp) *max_widthp = panel->mMaxWidth; - if (max_heightp) *max_heightp = panel->mMaxHeight; + if (max_dimp) *max_dimp = panel->mMaxDim; } return NULL != panel; @@ -465,25 +307,23 @@ void LLLayoutStack::updateLayout(BOOL force_resize) LLFastTimer ft(FTM_UPDATE_LAYOUT); static LLUICachedControl<S32> resize_bar_overlap ("UIResizeBarOverlap", 0); calcMinExtents(); + createResizeBars(); // 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)->mPanel; + LLPanel* panelp = (*panel_it); if (panelp->getVisible()) { if (mAnimate) { if (!mAnimatedThisFrame) { - (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_OPEN_TIME)); + (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 1.f, LLCriticalDamp::getInterpolant(mOpenTimeConstant)); if ((*panel_it)->mVisibleAmt > 0.99f) { (*panel_it)->mVisibleAmt = 1.f; @@ -501,7 +341,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize) { if (!mAnimatedThisFrame) { - (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 0.f, LLCriticalDamp::getInterpolant(mCloseTimeConstant)); if ((*panel_it)->mVisibleAmt < 0.001f) { (*panel_it)->mVisibleAmt = 0.f; @@ -516,21 +356,21 @@ void LLLayoutStack::updateLayout(BOOL force_resize) if ((*panel_it)->mCollapsed) { - (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 1.f, LLCriticalDamp::getInterpolant(mCloseTimeConstant)); } else { - (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 0.f, LLCriticalDamp::getInterpolant(mCloseTimeConstant)); } if (mOrientation == HORIZONTAL) { // enforce minimize size constraint by default - if (panelp->getRect().getWidth() < (*panel_it)->mMinWidth) + if (panelp->getRect().getWidth() < (*panel_it)->mMinDim) { - panelp->reshape((*panel_it)->mMinWidth, panelp->getRect().getHeight()); + panelp->reshape((*panel_it)->mMinDim, panelp->getRect().getHeight()); } - total_width += llround(panelp->getRect().getWidth() * (*panel_it)->getCollapseFactor()); + total_width += llround(panelp->getRect().getWidth() * (*panel_it)->getCollapseFactor(mOrientation)); // want n-1 panel gaps for n panels if (panel_it != mPanels.begin()) { @@ -540,11 +380,11 @@ void LLLayoutStack::updateLayout(BOOL force_resize) else //VERTICAL { // enforce minimize size constraint by default - if (panelp->getRect().getHeight() < (*panel_it)->mMinHeight) + if (panelp->getRect().getHeight() < (*panel_it)->mMinDim) { - panelp->reshape(panelp->getRect().getWidth(), (*panel_it)->mMinHeight); + panelp->reshape(panelp->getRect().getWidth(), (*panel_it)->mMinDim); } - total_height += llround(panelp->getRect().getHeight() * (*panel_it)->getCollapseFactor()); + total_height += llround(panelp->getRect().getHeight() * (*panel_it)->getCollapseFactor(mOrientation)); if (panel_it != mPanels.begin()) { total_height += mPanelSpacing; @@ -558,7 +398,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize) for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) { // panels that are not fully visible do not count towards shrink headroom - if ((*panel_it)->getCollapseFactor() < 1.f) + if ((*panel_it)->getCollapseFactor(mOrientation) < 1.f) { continue; } @@ -571,11 +411,11 @@ void LLLayoutStack::updateLayout(BOOL force_resize) { if (mOrientation == HORIZONTAL) { - shrink_headroom_total += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; + shrink_headroom_total += (*panel_it)->getRect().getWidth() - (*panel_it)->mMinDim; } else //VERTICAL { - shrink_headroom_total += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; + shrink_headroom_total += (*panel_it)->getRect().getHeight() - (*panel_it)->mMinDim; } } else @@ -583,13 +423,13 @@ void LLLayoutStack::updateLayout(BOOL force_resize) num_resizable_panels++; if (mOrientation == HORIZONTAL) { - shrink_headroom_available += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; - shrink_headroom_total += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; + 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)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; - shrink_headroom_total += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; + shrink_headroom_available += (*panel_it)->getRect().getHeight() - (*panel_it)->mMinDim; + shrink_headroom_total += (*panel_it)->getRect().getHeight() - (*panel_it)->mMinDim; } } } @@ -612,17 +452,25 @@ void LLLayoutStack::updateLayout(BOOL force_resize) for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) { - LLPanel* panelp = (*panel_it)->mPanel; + LLPanel* panelp = (*panel_it); S32 cur_width = panelp->getRect().getWidth(); S32 cur_height = panelp->getRect().getHeight(); - S32 new_width = llmax((*panel_it)->mMinWidth, cur_width); - S32 new_height = llmax((*panel_it)->mMinHeight, cur_height); + S32 new_width = cur_width; + S32 new_height = cur_height; + if (mOrientation == HORIZONTAL) + { + new_width = llmax((*panel_it)->mMinDim, new_width); + } + else + { + new_height = llmax((*panel_it)->mMinDim, new_height); + } S32 delta_size = 0; // if panel can automatically resize (not animating, and resize flag set)... - if ((*panel_it)->getCollapseFactor() == 1.f + if ((*panel_it)->getCollapseFactor(mOrientation) == 1.f && (force_resize || (*panel_it)->mAutoResize) && !(*panel_it)->mResizeBar->hasMouseCapture()) { @@ -633,8 +481,8 @@ void LLLayoutStack::updateLayout(BOOL force_resize) { // 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)->mMinWidth) / (F32)shrink_headroom_available)) : 0; - shrink_headroom_available -= (cur_width - (*panel_it)->mMinWidth); + 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 { @@ -643,7 +491,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize) num_resizable_panels--; } pixels_to_distribute -= delta_size; - new_width = llmax((*panel_it)->mMinWidth, cur_width + delta_size); + new_width = llmax((*panel_it)->mMinDim, cur_width + delta_size); } else { @@ -656,8 +504,8 @@ void LLLayoutStack::updateLayout(BOOL force_resize) { // 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)->mMinHeight) / (F32)shrink_headroom_available)) : 0; - shrink_headroom_available -= (cur_height - (*panel_it)->mMinHeight); + 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); } else { @@ -665,7 +513,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize) num_resizable_panels--; } pixels_to_distribute -= delta_size; - new_height = llmax((*panel_it)->mMinHeight, cur_height + delta_size); + new_height = llmax((*panel_it)->mMinDim, cur_height + delta_size); } else { @@ -706,48 +554,48 @@ void LLLayoutStack::updateLayout(BOOL force_resize) if (mOrientation == HORIZONTAL) { - cur_x += llround(new_width * (*panel_it)->getCollapseFactor()) + mPanelSpacing; + cur_x += llround(new_width * (*panel_it)->getCollapseFactor(mOrientation)) + mPanelSpacing; } else //VERTICAL { - cur_y -= llround(new_height * (*panel_it)->getCollapseFactor()) + mPanelSpacing; + cur_y -= llround(new_height * (*panel_it)->getCollapseFactor(mOrientation)) + mPanelSpacing; } } // update resize bars with new limits - LLResizeBar* last_resize_bar = NULL; + LLLayoutPanel* last_resizeable_panel = NULL; for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) { - LLPanel* panelp = (*panel_it)->mPanel; + LLPanel* panelp = (*panel_it); if (mOrientation == HORIZONTAL) { (*panel_it)->mResizeBar->setResizeLimits( - (*panel_it)->mMinWidth, - (*panel_it)->mMinWidth + shrink_headroom_total); + (*panel_it)->mMinDim, + (*panel_it)->mMinDim + shrink_headroom_total); } else //VERTICAL { (*panel_it)->mResizeBar->setResizeLimits( - (*panel_it)->mMinHeight, - (*panel_it)->mMinHeight + shrink_headroom_total); + (*panel_it)->mMinDim, + (*panel_it)->mMinDim + shrink_headroom_total); } // 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 ((*panel_it)->mUserResize || (*panel_it)->mAutoResize) { - last_resize_bar = (*panel_it)->mResizeBar; + last_resizeable_panel = (*panel_it); } } // hide last resize bar as there is nothing past it // resize bars need to be in between two resizable panels - if (last_resize_bar) + if (last_resizeable_panel) { - last_resize_bar->setVisible(FALSE); + last_resizeable_panel->mResizeBar->setVisible(FALSE); } // not enough room to fit existing contents @@ -766,14 +614,14 @@ void LLLayoutStack::updateLayout(BOOL force_resize) } // end LLLayoutStack::updateLayout -LLLayoutStack::LayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const +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) { - if ((*panel_it)->mPanel == panelp) + if ((*panel_it) == panelp) { return *panel_it; } @@ -781,15 +629,15 @@ LLLayoutStack::LayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) co return NULL; } -LLLayoutStack::LayoutPanel* LLLayoutStack::findEmbeddedPanelByName(const std::string& name) const +LLLayoutPanel* LLLayoutStack::findEmbeddedPanelByName(const std::string& name) const { - LayoutPanel* result = NULL; + LLLayoutPanel* result = NULL; for (e_panel_list_t::const_iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) { - LayoutPanel* p = *panel_it; + LLLayoutPanel* p = *panel_it; - if (p->mPanel->getName() == name) + if (p->getName() == name) { result = p; break; @@ -810,9 +658,7 @@ void LLLayoutStack::calcMinExtents() { if (mOrientation == HORIZONTAL) { - mMinHeight = llmax( mMinHeight, - (*panel_it)->mMinHeight); - mMinWidth += (*panel_it)->mMinWidth; + mMinWidth += (*panel_it)->mMinDim; if (panel_it != mPanels.begin()) { mMinWidth += mPanelSpacing; @@ -820,9 +666,7 @@ void LLLayoutStack::calcMinExtents() } else //VERTICAL { - mMinWidth = llmax( mMinWidth, - (*panel_it)->mMinWidth); - mMinHeight += (*panel_it)->mMinHeight; + mMinHeight += (*panel_it)->mMinDim; if (panel_it != mPanels.begin()) { mMinHeight += mPanelSpacing; @@ -831,6 +675,37 @@ void LLLayoutStack::calcMinExtents() } } +void LLLayoutStack::createResizeBars() +{ + for (e_panel_list_t::iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLLayoutPanel* lp = (*panel_it); + if (lp->mResizeBar == NULL) + { + 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); + + // 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); + } + } + } +} + // 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 diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h index cd59ee3966..4ac8ef0ee9 100644 --- a/indra/llui/lllayoutstack.h +++ b/indra/llui/lllayoutstack.h @@ -28,34 +28,50 @@ #ifndef LL_LLLAYOUTSTACK_H #define LL_LLLAYOUTSTACK_H -#include "llview.h" +#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> { - Optional<std::string> orientation; + Mandatory<ELayoutOrientation, OrientationNames> orientation; Optional<S32> border_size; Optional<bool> animate, clip; + Optional<F32> open_time_constant, + close_time_constant; Params(); }; - typedef enum e_layout_orientation - { - HORIZONTAL, - VERTICAL - } ELayoutOrientation; + typedef LayoutStackRegistry child_registry_t; virtual ~LLLayoutStack(); /*virtual*/ void draw(); /*virtual*/ void removeChild(LLView*); /*virtual*/ BOOL postBuild(); + /*virtual*/ bool addChild(LLView* child, S32 tab_group = 0); static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); @@ -68,29 +84,32 @@ public: ANIMATE } EAnimate; - void addPanel(LLPanel* panel, S32 min_width, S32 min_height, S32 max_width, S32 max_height, BOOL auto_resize, BOOL user_resize, EAnimate animate = NO_ANIMATE, S32 index = S32_MAX); + 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 width and/or height of the specified by name panel. + * Gets minimal dimension along layout_stack axis of the specified by name panel. * - * If it is necessary to get only the one dimension pass NULL for another one. * @returns true if specified by panel_name internal panel exists, false otherwise. */ - bool getPanelMinSize(const std::string& panel_name, S32* min_widthp, S32* min_heightp); + bool getPanelMinSize(const std::string& panel_name, S32* min_dimp); /** - * Gets maximal width and/or height of the specified by name panel. + * Gets maximal dimension along layout_stack axis of the specified by name panel. * - * If it is necessary to get only the one dimension pass NULL for another one. * @returns true if specified by panel_name internal panel exists, false otherwise. */ - bool getPanelMaxSize(const std::string& panel_name, S32* max_width, S32* max_height); + bool getPanelMaxSize(const std::string& panel_name, S32* max_dim); void updateLayout(BOOL force_resize = FALSE); @@ -105,19 +124,18 @@ protected: friend class LLUICtrlFactory; private: - struct LayoutPanel; - + void createResizeBars(); void calcMinExtents(); S32 getDefaultHeight(S32 cur_height); S32 getDefaultWidth(S32 cur_width); const ELayoutOrientation mOrientation; - typedef std::vector<LayoutPanel*> e_panel_list_t; + typedef std::vector<LLLayoutPanel*> e_panel_list_t; e_panel_list_t mPanels; - LayoutPanel* findEmbeddedPanel(LLPanel* panelp) const; - LayoutPanel* findEmbeddedPanelByName(const std::string& name) const; + LLLayoutPanel* findEmbeddedPanel(LLPanel* panelp) const; + LLLayoutPanel* findEmbeddedPanelByName(const std::string& name) const; S32 mMinWidth; // calculated by calcMinExtents S32 mMinHeight; // calculated by calcMinExtents @@ -127,6 +145,56 @@ private: bool mAnimatedThisFrame; bool mAnimate; bool mClip; + F32 mOpenTimeConstant; + F32 mCloseTimeConstant; }; // end class LLLayoutStack +class LLLayoutPanel : public LLPanel +{ +friend class LLLayoutStack; +friend class LLUICtrlFactory; +public: + struct Params : public LLInitParam::Block<Params, LLPanel::Params> + { + Optional<S32> min_dim, + max_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"); + } + }; + + ~LLLayoutPanel(); + + void initFromParams(const Params& p); + void setMinDim(S32 value) { mMinDim = value; } + void setMaxDim(S32 value) { mMaxDim = value; } + +protected: + LLLayoutPanel(const Params& p) ; + + + F32 getCollapseFactor(LLLayoutStack::ELayoutOrientation orientation); + + S32 mMinDim; + S32 mMaxDim; + BOOL mAutoResize; + BOOL mUserResize; + BOOL mCollapsed; + class LLResizeBar* mResizeBar; + F32 mVisibleAmt; + F32 mCollapseAmt; +}; + + #endif diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 2759167d04..0196080d90 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -78,7 +78,7 @@ template class LLLineEditor* LLView::getChild<class LLLineEditor>( // LLLineEditor::Params::Params() -: max_length_bytes("max_length", 254), +: max_length(""), keystroke_callback("keystroke_callback"), prevalidate_callback("prevalidate_callback"), background_image("background_image"), @@ -88,6 +88,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"), @@ -108,7 +109,8 @@ LLLineEditor::Params::Params() LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) : LLUICtrl(p), - mMaxLengthBytes(p.max_length_bytes), + mMaxLengthBytes(p.max_length.bytes), + mMaxLengthChars(p.max_length.chars), mCursorPos( 0 ), mScrollHPos( 0 ), mTextPadLeft(p.text_pad_left), @@ -128,7 +130,7 @@ 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 ), mPassDelete(FALSE), mReadOnly(FALSE), @@ -313,6 +315,12 @@ void LLLineEditor::setMaxTextLength(S32 max_text_length) mMaxLengthBytes = max_len; } +void LLLineEditor::setMaxTextChars(S32 max_text_chars) +{ + S32 max_chars = llmax(0, max_text_chars); + mMaxLengthChars = max_chars; +} + void LLLineEditor::getTextPadding(S32 *left, S32 *right) { *left = mTextPadLeft; @@ -358,6 +366,16 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) } mText.assign(truncated_utf8); + if (mMaxLengthChars) + { + LLWString truncated_wstring = utf8str_to_wstring(truncated_utf8); + if (truncated_wstring.size() > (U32)mMaxLengthChars) + { + truncated_wstring = truncated_wstring.substr(0, mMaxLengthChars); + } + mText.assign(wstring_to_utf8str(truncated_wstring)); + } + if (all_selected) { // ...keep whole thing selected @@ -371,7 +389,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; } @@ -798,6 +820,7 @@ void LLLineEditor::addChar(const llwchar uni_char) } S32 cur_bytes = mText.getString().size(); + S32 new_bytes = wchar_utf8_length(new_c); BOOL allow_char = TRUE; @@ -807,6 +830,14 @@ void LLLineEditor::addChar(const llwchar uni_char) { allow_char = FALSE; } + else if (mMaxLengthChars) + { + S32 wide_chars = mText.getWString().size(); + if ((wide_chars + 1) > mMaxLengthChars) + { + allow_char = FALSE; + } + } if (allow_char) { @@ -1107,7 +1138,19 @@ void LLLineEditor::pasteHelper(bool is_primary) clean_string = clean_string.substr(0, wchars_that_fit); LLUI::reportBadKeystroke(); } - + + if (mMaxLengthChars) + { + U32 available_chars = mMaxLengthChars - mText.getWString().size(); + + if (available_chars < clean_string.size()) + { + clean_string = clean_string.substr(0, available_chars); + } + + LLUI::reportBadKeystroke(); + } + mText.insert(getCursor(), clean_string); setCursor( getCursor() + (S32)clean_string.length() ); deselect(); @@ -1266,12 +1309,12 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) // handle ctrl-uparrow if we have a history enabled line editor. case KEY_UP: - if( mHaveHistory && ( MASK_CONTROL == mask ) ) + if( mHaveHistory && ((mIgnoreArrowKeys == false) || ( MASK_CONTROL == mask )) ) { if( mCurrentHistoryLine > mLineHistory.begin() ) { mText.assign( *(--mCurrentHistoryLine) ); - setCursor(llmin((S32)mText.length(), getCursor())); + setCursorToEnd(); } else { @@ -1281,14 +1324,14 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) } break; - // handle ctrl-downarrow if we have a history enabled line editor + // handle [ctrl]-downarrow if we have a history enabled line editor case KEY_DOWN: - if( mHaveHistory && ( MASK_CONTROL == mask ) ) + if( mHaveHistory && ((mIgnoreArrowKeys == false) || ( MASK_CONTROL == mask )) ) { if( !mLineHistory.empty() && mCurrentHistoryLine < mLineHistory.end() - 1 ) { mText.assign( *(++mCurrentHistoryLine) ); - setCursor(llmin((S32)mText.length(), getCursor())); + setCursorToEnd(); } else { @@ -1491,8 +1534,11 @@ void LLLineEditor::drawBackground() { image = mBgImage; } + + if (!image) return; - F32 alpha = getDrawContext().mAlpha; + F32 alpha = getCurrentTransparency(); + // optionally draw programmatic border if (has_focus) { @@ -2248,3 +2294,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 76d0187712..fe191e5971 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,11 +60,19 @@ public: typedef boost::function<void (LLLineEditor* caller)> keystroke_callback_t; + struct MaxLength : public LLInitParam::Choice<MaxLength> + { + Alternative<S32> bytes, chars; + + MaxLength() : bytes("max_length_bytes", 254), + chars("max_length_chars", 0) + {} + }; + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { Optional<std::string> default_text; - Optional<S32> max_length_bytes; - + Optional<MaxLength> max_length; Optional<keystroke_callback_t> keystroke_callback; Optional<LLTextValidate::validate_func_t, LLTextValidate::ValidateTextNamedFuncs> prevalidate_callback; @@ -77,7 +86,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, @@ -192,6 +202,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; } @@ -214,6 +225,7 @@ public: void setKeystrokeCallback(callback_t callback, void* user_data); void setMaxTextLength(S32 max_text_length); + void setMaxTextChars(S32 max_text_chars); // Manipulate left and right padding for text void getTextPadding(S32 *left, S32 *right); void setTextPadding(S32 left, S32 right); @@ -277,6 +289,7 @@ protected: LLViewBorder* mBorder; const LLFontGL* mGLFont; S32 mMaxLengthBytes; // Max length of the UTF8 string in bytes + S32 mMaxLengthChars; // Maximum number of characters in the string S32 mCursorPos; // I-beam is just after the mCursorPos-th character. S32 mScrollHPos; // Horizontal offset from the start of mText. Used for scrolling. LLFrameTimer mScrollTimer; @@ -326,7 +339,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..c4eec1835c 100644 --- a/indra/llui/llloadingindicator.cpp +++ b/indra/llui/llloadingindicator.cpp @@ -39,56 +39,24 @@ //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) + for (LLInitParam::ParamIterator<LLUIImage*>::const_iterator it = p.images().image.begin(), end_it = p.images().image.end(); + it != end_it; + ++it) { - std::string img_name = llformat("Progress_%d", i+1); - mImages[i] = LLUI::getUIImage(img_name, 0); - llassert(mImages[i]); + mImages.push_back(it->getValue()); } -} - -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 +68,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 +96,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..4c47cc267c 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::Block<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; + Batch<Images> images; + Params() - : rotations_per_sec("rotations_per_sec", 1.0f) + : images_per_sec("images_per_sec", 1.0f), + images("images") {} }; @@ -74,14 +88,15 @@ public: 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 3df05f4d3f..eed0085273 100644 --- a/indra/llui/llmenubutton.cpp +++ b/indra/llui/llmenubutton.cpp @@ -29,7 +29,7 @@ #include "llmenubutton.h" // Linden library includes -#include "llmenugl.h" +#include "lltoggleablemenu.h" #include "llstring.h" #include "v4color.h" @@ -44,58 +44,77 @@ LLMenuButton::Params::Params() LLMenuButton::LLMenuButton(const LLMenuButton::Params& p) : LLButton(p), - mMenu(NULL), - mMenuVisibleLastFrame(false) + mIsMenuShown(false), + mMenuPosition(MP_BOTTOM_LEFT) { std::string menu_filename = p.menu_filename; if (!menu_filename.empty()) { - mMenu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>(menu_filename, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); - if (!mMenu) + LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>(menu_filename, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + if (!menu) { llwarns << "Error loading menu_button menu" << llendl; + return; } + + menu->setVisibilityChangeCallback(boost::bind(&LLMenuButton::onMenuVisibilityChange, this, _2)); + + mMenuHandle = menu->getHandle(); + + updateMenuOrigin(); } } -void LLMenuButton::toggleMenu() +boost::signals2::connection LLMenuButton::setMouseDownCallback( const mouse_signal_t::slot_type& cb ) { - if(!mMenu) - return; + return LLUICtrl::setMouseDownCallback(cb); +} - if (mMenu->getVisible() || mMenuVisibleLastFrame) - { - mMenu->setVisible(FALSE); - } - else +void LLMenuButton::hideMenu() +{ + if(mMenuHandle.isDead()) return; + + LLToggleableMenu* menu = dynamic_cast<LLToggleableMenu*>(mMenuHandle.get()); + if (menu) { - LLRect rect = getRect(); - //mMenu->needsArrange(); //so it recalculates the visible elements - LLMenuGL::showPopup(getParent(), mMenu, rect.mLeft, rect.mBottom); + menu->setVisible(FALSE); } } - -void LLMenuButton::hideMenu() -{ - if(!mMenu) - return; - mMenu->setVisible(FALSE); +LLToggleableMenu* LLMenuButton::getMenu() +{ + return dynamic_cast<LLToggleableMenu*>(mMenuHandle.get()); } +void LLMenuButton::setMenu(LLToggleableMenu* menu, EMenuPosition position /*MP_TOP_LEFT*/) +{ + if (!menu) return; + + mMenuHandle = menu->getHandle(); + mMenuPosition = position; + + menu->setVisibilityChangeCallback(boost::bind(&LLMenuButton::onMenuVisibilityChange, this, _2)); +} BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask ) { + if (mMenuHandle.isDead()) return FALSE; + if( KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) { + // *HACK: We emit the mouse down signal to fire the callback bound to the + // menu emerging event before actually displaying the menu. See STORM-263. + LLUICtrl::handleMouseDown(-1, -1, MASK_NONE); + toggleMenu(); return TRUE; } - if (mMenu && mMenu->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE) + LLToggleableMenu* menu = dynamic_cast<LLToggleableMenu*>(mMenuHandle.get()); + if (menu && menu->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE) { - mMenu->setVisible(FALSE); + menu->setVisible(FALSE); return TRUE; } @@ -104,34 +123,84 @@ BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask ) BOOL LLMenuButton::handleMouseDown(S32 x, S32 y, MASK mask) { - if (hasTabStop() && !getIsChrome()) - { - setFocus(TRUE); - } + LLButton::handleMouseDown(x, y, mask); toggleMenu(); - if (getSoundFlags() & MOUSE_DOWN) - { - make_ui_sound("UISndClick"); - } - return TRUE; } -void LLMenuButton::draw() +void LLMenuButton::toggleMenu() { - //we save this off so next frame when we try to close it by - //button click, and it hides menus before we get to it, we know - mMenuVisibleLastFrame = mMenu && mMenu->getVisible(); - - if (mMenuVisibleLastFrame) + if(mMenuHandle.isDead()) return; + + LLToggleableMenu* menu = dynamic_cast<LLToggleableMenu*>(mMenuHandle.get()); + if (!menu) return; + + // Store the button rectangle to toggle menu visibility if a mouse event + // occurred inside or outside the button rect. + menu->setButtonRect(this); + + if (!menu->toggleVisibility() && mIsMenuShown) { + setForcePressedState(false); + mIsMenuShown = false; + } + else + { + menu->buildDrawLabels(); + menu->arrangeAndClear(); + menu->updateParent(LLMenuGL::sMenuContainer); + + updateMenuOrigin(); + + LLMenuGL::showPopup(getParent(), menu, mX, mY); + setForcePressedState(true); + mIsMenuShown = true; } +} - LLButton::draw(); +void LLMenuButton::updateMenuOrigin() +{ + if (mMenuHandle.isDead()) return; + + LLRect rect = getRect(); - setForcePressedState(false); + switch (mMenuPosition) + { + case MP_TOP_LEFT: + { + mX = rect.mLeft; + 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; + mY = rect.mBottom; + break; + } + } } +void LLMenuButton::onMenuVisibilityChange(const LLSD& param) +{ + bool new_visibility = param["visibility"].asBoolean(); + bool is_closed_by_button_click = param["closed_by_button_click"].asBoolean(); + + // Reset the button "pressed" state only if the menu is shown by this particular + // menu button (not any other control) and is not being closed by a click on the button. + if (!new_visibility && !is_closed_by_button_click && mIsMenuShown) + { + setForcePressedState(false); + mIsMenuShown = false; + } +} diff --git a/indra/llui/llmenubutton.h b/indra/llui/llmenubutton.h index 81ca0e047c..7b657595da 100644 --- a/indra/llui/llmenubutton.h +++ b/indra/llui/llmenubutton.h @@ -29,7 +29,7 @@ #include "llbutton.h" -class LLMenuGL; +class LLToggleableMenu; class LLMenuButton : public LLButton @@ -42,22 +42,42 @@ public: Optional<std::string> menu_filename; Params(); - }; + }; + + typedef enum e_menu_position + { + MP_TOP_LEFT, + MP_TOP_RIGHT, + MP_BOTTOM_LEFT + } EMenuPosition; - void toggleMenu(); - /*virtual*/ void draw(); + boost::signals2::connection setMouseDownCallback( const mouse_signal_t::slot_type& cb ); + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); + void hideMenu(); - LLMenuGL* getMenu() { return mMenu; } + + LLToggleableMenu* getMenu(); + void setMenu(LLToggleableMenu* menu, EMenuPosition position = MP_TOP_LEFT); + + void setMenuPosition(EMenuPosition position) { mMenuPosition = position; } protected: friend class LLUICtrlFactory; LLMenuButton(const Params&); + void toggleMenu(); + void updateMenuOrigin(); + + void onMenuVisibilityChange(const LLSD& param); + private: - LLMenuGL* mMenu; - bool mMenuVisibleLastFrame; + LLHandle<LLView> mMenuHandle; + bool mIsMenuShown; + EMenuPosition mMenuPosition; + S32 mX; + S32 mY; }; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 900a814238..8de9c769e2 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -1462,7 +1462,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 +1637,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); @@ -1848,89 +1852,110 @@ BOOL LLMenuGL::isOpen() } } -void LLMenuGL::scrollItemsUp() + + +bool LLMenuGL::scrollItems(EScrollingDirection direction) { - // Slowing down the items scrolling when arrow button is held + // Slowing down items scrolling when arrow button is held if (mScrollItemsTimer.hasExpired() && NULL != mFirstVisibleItem) { mScrollItemsTimer.setTimerExpirySec(.033f); } else { - return; + return false; } - item_list_t::iterator cur_item_iter; - item_list_t::iterator prev_item_iter; - for (cur_item_iter = mItems.begin(), prev_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) + switch (direction) { - if( (*cur_item_iter) == mFirstVisibleItem) + case SD_UP: + { + item_list_t::iterator cur_item_iter; + item_list_t::iterator prev_item_iter; + for (cur_item_iter = mItems.begin(), prev_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) { - break; + if( (*cur_item_iter) == mFirstVisibleItem) + { + break; + } + if ((*cur_item_iter)->getVisible()) + { + prev_item_iter = cur_item_iter; + } } - if ((*cur_item_iter)->getVisible()) + + if ((*prev_item_iter)->getVisible()) { - prev_item_iter = cur_item_iter; + mFirstVisibleItem = *prev_item_iter; } + break; } - - if ((*prev_item_iter)->getVisible()) - { - mFirstVisibleItem = *prev_item_iter; - } - - mNeedsArrange = TRUE; - arrangeAndClear(); -} - -void LLMenuGL::scrollItemsDown() -{ - // Slowing down the items scrolling when arrow button is held - if (mScrollItemsTimer.hasExpired()) - { - mScrollItemsTimer.setTimerExpirySec(.033f); - } - else + case SD_DOWN: { - return; - } - - if (NULL == mFirstVisibleItem) - { - mFirstVisibleItem = *mItems.begin(); - } - - item_list_t::iterator cur_item_iter; - - for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) - { - if( (*cur_item_iter) == mFirstVisibleItem) + if (NULL == mFirstVisibleItem) { - break; + mFirstVisibleItem = *mItems.begin(); } - } - item_list_t::iterator next_item_iter; + item_list_t::iterator cur_item_iter; - if (cur_item_iter != mItems.end()) - { - for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) + for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) { - if( (*next_item_iter)->getVisible()) + if( (*cur_item_iter) == mFirstVisibleItem) { break; } } - - if (next_item_iter != mItems.end() && - (*next_item_iter)->getVisible()) + + item_list_t::iterator next_item_iter; + + if (cur_item_iter != mItems.end()) { - mFirstVisibleItem = *next_item_iter; + for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) + { + if( (*next_item_iter)->getVisible()) + { + break; + } + } + + if (next_item_iter != mItems.end() && + (*next_item_iter)->getVisible()) + { + mFirstVisibleItem = *next_item_iter; + } } + break; } - + case SD_BEGIN: + { + mFirstVisibleItem = *mItems.begin(); + break; + } + case SD_END: + { + 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, scrollable_items); + mFirstVisibleItem = *first_visible_item_iter; + break; + } + default: + llwarns << "Unknown scrolling direction: " << direction << llendl; + } + mNeedsArrange = TRUE; arrangeAndClear(); + + return true; } // rearrange the child rects so they fit the shape of the menu. @@ -2162,7 +2187,7 @@ void LLMenuGL::arrange( void ) LLMenuScrollItem::Params item_params; item_params.name(ARROW_UP); item_params.arrow_type(LLMenuScrollItem::ARROW_UP); - item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItemsUp, this)); + item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_UP)); mArrowUpItem = LLUICtrlFactory::create<LLMenuScrollItem>(item_params); LLUICtrl::addChild(mArrowUpItem); @@ -2173,7 +2198,7 @@ void LLMenuGL::arrange( void ) LLMenuScrollItem::Params item_params; item_params.name(ARROW_DOWN); item_params.arrow_type(LLMenuScrollItem::ARROW_DOWN); - item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItemsDown, this)); + item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_DOWN)); mArrowDownItem = LLUICtrlFactory::create<LLMenuScrollItem>(item_params); LLUICtrl::addChild(mArrowDownItem); @@ -2596,6 +2621,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()) @@ -2603,14 +2629,8 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa ((LLFloater*)getParent())->setFocus(TRUE); } - item_list_t::iterator cur_item_iter; - for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); ++cur_item_iter) - { - if( (*cur_item_iter) == cur_item) - { - break; - } - } + // Current item position in the items list + item_list_t::iterator cur_item_iter = std::find(mItems.begin(), mItems.end(), cur_item); item_list_t::iterator next_item_iter; if (cur_item_iter == mItems.end()) @@ -2621,9 +2641,37 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa { next_item_iter = cur_item_iter; next_item_iter++; + + // First visible item position in the items list + item_list_t::iterator first_visible_item_iter = std::find(mItems.begin(), mItems.end(), mFirstVisibleItem); + if (next_item_iter == mItems.end()) { next_item_iter = mItems.begin(); + + // If current item is the last in the list, the menu is scrolled to the beginning + // and the first item is highlighted. + if (mScrollable && !scrollItems(SD_BEGIN)) + { + return NULL; + } + } + // If current item is the last visible, the menu is scrolled one item down + // and the next item is highlighted. + else if (mScrollable && + (U32)std::abs(std::distance(first_visible_item_iter, next_item_iter)) >= mMaxScrollableItems) + { + // Call highlightNextItem() recursively only if the menu was successfully scrolled down. + // If scroll timer hasn't expired yet the menu won't be scrolled and calling + // highlightNextItem() will result in an endless recursion. + if (scrollItems(SD_DOWN)) + { + return highlightNextItem(cur_item, skip_disabled); + } + else + { + return NULL; + } } } @@ -2674,6 +2722,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()) @@ -2681,14 +2731,8 @@ LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disa ((LLFloater*)getParent())->setFocus(TRUE); } - item_list_t::reverse_iterator cur_item_iter; - for (cur_item_iter = mItems.rbegin(); cur_item_iter != mItems.rend(); ++cur_item_iter) - { - if( (*cur_item_iter) == cur_item) - { - break; - } - } + // Current item reverse position from the end of the list + item_list_t::reverse_iterator cur_item_iter = std::find(mItems.rbegin(), mItems.rend(), cur_item); item_list_t::reverse_iterator prev_item_iter; if (cur_item_iter == mItems.rend()) @@ -2699,9 +2743,37 @@ LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disa { prev_item_iter = cur_item_iter; prev_item_iter++; + + // First visible item reverse position in the items list + item_list_t::reverse_iterator first_visible_item_iter = std::find(mItems.rbegin(), mItems.rend(), mFirstVisibleItem); + if (prev_item_iter == mItems.rend()) { prev_item_iter = mItems.rbegin(); + + // If current item is the first in the list, the menu is scrolled to the end + // and the last item is highlighted. + if (mScrollable && !scrollItems(SD_END)) + { + return NULL; + } + } + // If current item is the first visible, the menu is scrolled one item up + // and the previous item is highlighted. + else if (mScrollable && + std::distance(first_visible_item_iter, cur_item_iter) <= 0) + { + // Call highlightNextItem() only if the menu was successfully scrolled up. + // If scroll timer hasn't expired yet the menu won't be scrolled and calling + // highlightNextItem() will result in an endless recursion. + if (scrollItems(SD_UP)) + { + return highlightPrevItem(cur_item, skip_disabled); + } + else + { + return NULL; + } } } @@ -2872,12 +2944,12 @@ BOOL LLMenuGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) if( clicks > 0 ) { while( clicks-- ) - scrollItemsDown(); + scrollItems(SD_DOWN); } else { while( clicks++ ) - scrollItemsUp(); + scrollItems(SD_UP); } return TRUE; @@ -2986,6 +3058,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. @@ -3028,9 +3105,6 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2); menu->translateIntoRectWithExclusion( menu_region_rect, mouse_rect, FALSE ); menu->getParent()->sendChildToFront(menu); - - - } ///============================================================================ @@ -3066,7 +3140,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()) { @@ -3449,8 +3526,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; + } } } } @@ -3683,9 +3762,7 @@ public: LLContextMenuBranch(const Params&); virtual ~LLContextMenuBranch() - { - delete mBranch; - } + {} // called to rebuild the draw label virtual void buildDrawLabel( void ); @@ -3693,21 +3770,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 @@ -3716,12 +3793,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() ) { @@ -3738,19 +3815,18 @@ void LLContextMenuBranch::buildDrawLabel( void ) appendAcceleratorString( st ); mDrawAccelLabel = st; - // No special branch suffix - mDrawBranchLabel.clear(); + mDrawBranchLabel = LLMenuGL::BRANCH_SUFFIX; } 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); } } @@ -3770,7 +3846,7 @@ void LLContextMenuBranch::setHighlight( BOOL highlight ) } else { - mBranch->hide(); + mBranch.get()->hide(); } } @@ -3801,6 +3877,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 19b738312e..7bde8e83ec 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -397,6 +397,15 @@ public: static const std::string ARROW_UP; static const std::string ARROW_DOWN; + // for scrollable menus + typedef enum e_scrolling_direction + { + SD_UP = 0, + SD_DOWN = 1, + SD_BEGIN = 2, + SD_END = 3 + } EScrollingDirection; + protected: LLMenuGL(const LLMenuGL::Params& p); friend class LLUICtrlFactory; @@ -503,8 +512,7 @@ public: S32 getShortcutPad() { return mShortcutPad; } - void scrollItemsUp(); - void scrollItemsDown(); + bool scrollItems(EScrollingDirection direction); BOOL isScrollable() const { return mScrollable; } static class LLMenuHolderGL* sMenuContainer; @@ -670,9 +678,12 @@ public: BOOL appendContextSubMenu(LLContextMenu *menu); + LLHandle<LLContextMenu> getHandle() { mHandle.bind(this); return mHandle; } + protected: - BOOL mHoveredAnyItem; - LLMenuItemGL* mHoverItem; + BOOL mHoveredAnyItem; + LLMenuItemGL* mHoverItem; + LLRootHandle<LLContextMenu> mHandle; }; diff --git a/indra/llui/llmultislider.cpp b/indra/llui/llmultislider.cpp index 1f6fa12969..f744e9db41 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> @@ -101,8 +102,8 @@ LLMultiSlider::LLMultiSlider(const LLMultiSlider::Params& p) setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); } - for (LLInitParam::ParamIterator<SliderParams>::const_iterator it = p.sliders().begin(); - it != p.sliders().end(); + for (LLInitParam::ParamIterator<SliderParams>::const_iterator it = p.sliders.begin(); + it != p.sliders.end(); ++it) { if (it->name.isProvided()) diff --git a/indra/llui/llmultisliderctrl.cpp b/indra/llui/llmultisliderctrl.cpp index bd65625f53..91e5b6b9de 100644 --- a/indra/llui/llmultisliderctrl.cpp +++ b/indra/llui/llmultisliderctrl.cpp @@ -130,7 +130,7 @@ LLMultiSliderCtrl::LLMultiSliderCtrl(const LLMultiSliderCtrl::Params& p) params.name("MultiSliderCtrl Editor"); params.rect(text_rect); params.font(p.font); - params.max_length_bytes(MAX_STRING_LENGTH); + params.max_length.bytes(MAX_STRING_LENGTH); params.commit_callback.function(LLMultiSliderCtrl::onEditorCommit); params.prevalidate_callback(&LLTextValidate::validateFloat); params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 99d540a9de..6085c61f9a 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -27,8 +27,12 @@ #include "linden_common.h" #include "llnotifications.h" +#include "llnotificationtemplate.h" +#include "llnotificationvisibilityrule.h" +#include "llavatarnamecache.h" #include "llinstantmessage.h" +#include "llcachename.h" #include "llxmlnode.h" #include "lluictrl.h" #include "lluictrlfactory.h" @@ -37,6 +41,8 @@ #include "lltrans.h" #include "llnotificationslistener.h" #include "llstring.h" +#include "llsdparam.h" +#include "llsdutil.h" #include <algorithm> #include <boost/regex.hpp> @@ -44,6 +50,59 @@ const std::string NOTIFICATION_PERSIST_VERSION = "0.93"; +void NotificationPriorityValues::declareValues() +{ + declare("low", NOTIFICATION_PRIORITY_LOW); + declare("normal", NOTIFICATION_PRIORITY_NORMAL); + declare("high", NOTIFICATION_PRIORITY_HIGH); + declare("critical", NOTIFICATION_PRIORITY_CRITICAL); +} + +LLNotificationForm::FormElementBase::FormElementBase() +: name("name") +{} + +LLNotificationForm::FormIgnore::FormIgnore() +: text("text"), + control("control"), + invert_control("invert_control", false), + save_option("save_option", false) +{} + +LLNotificationForm::FormButton::FormButton() +: index("index"), + text("text"), + ignore("ignore"), + is_default("default"), + type("type") +{ + // set type here so it gets serialized + type = "button"; +} + +LLNotificationForm::FormInput::FormInput() +: type("type"), + text("text"), + max_length_chars("max_length_chars"), + width("width", 0), + value("value") +{} + +LLNotificationForm::FormElement::FormElement() +: button("button"), + input("input") +{} + +LLNotificationForm::FormElements::FormElements() +: elements("") +{} + +LLNotificationForm::Params::Params() +: name("name"), + ignore("ignore"), + form_elements("") +{} + // Local channel for persistent notifications // Stores only persistent notifications. // Class users can use connectChanged() to process persistent notifications @@ -80,20 +139,9 @@ 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 - if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) - { - return LLUI::sSettingGroups["ignores"]->getBOOL(notification->getName()); - } - - return true; + return !notification->getForm()->getIgnored(); } bool handleIgnoredNotification(const LLSD& payload) @@ -125,6 +173,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 @@ -135,63 +205,68 @@ namespace LLNotificationFilters }; LLNotificationForm::LLNotificationForm() -: mFormData(LLSD::emptyArray()), - mIgnore(IGNORE_NO) +: mIgnore(IGNORE_NO) { } -LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node) -: mFormData(LLSD::emptyArray()), - mIgnore(IGNORE_NO) +LLNotificationForm::LLNotificationForm(const std::string& name, const LLNotificationForm::Params& p) +: mIgnore(IGNORE_NO), + mInvertSetting(false) // ignore settings by default mean true=show, false=ignore { - if (!xml_node->hasName("form")) + if (p.ignore.isProvided()) { - llwarns << "Bad xml node for form: " << xml_node->getName() << llendl; - } - LLXMLNodePtr child = xml_node->getFirstChild(); - while(child) - { - child = LLNotifications::instance().checkForXMLTemplate(child); + mIgnoreMsg = p.ignore.text; - LLSD item_entry; - std::string element_name = child->getName()->mString; + if (!p.ignore.save_option) + { + mIgnore = IGNORE_WITH_DEFAULT_RESPONSE; + } + else + { + // remember last option chosen by user and automatically respond with that in the future + mIgnore = IGNORE_WITH_LAST_RESPONSE; + LLUI::sSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); + } - if (element_name == "ignore" ) + BOOL show_notification = TRUE; + if (p.ignore.control.isProvided()) { - bool save_option = false; - child->getAttribute_bool("save_option", save_option); - if (!save_option) - { - mIgnore = IGNORE_WITH_DEFAULT_RESPONSE; - } - else - { - // remember last option chosen by user and automatically respond with that in the future - mIgnore = IGNORE_WITH_LAST_RESPONSE; - LLUI::sSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); - } - child->getAttributeString("text", mIgnoreMsg); - BOOL show_notification = TRUE; - LLUI::sSettingGroups["ignores"]->declareBOOL(name, show_notification, "Ignore notification with this name", TRUE); + mIgnoreSetting = LLUI::sSettingGroups["config"]->getControl(p.ignore.control); + mInvertSetting = p.ignore.invert_control; } else { - // flatten xml form entry into single LLSD map with type==name - item_entry["type"] = element_name; - const LLXMLAttribList::iterator attrib_end = child->mAttributes.end(); - for(LLXMLAttribList::iterator attrib_it = child->mAttributes.begin(); - attrib_it != attrib_end; - ++attrib_it) - { - item_entry[std::string(attrib_it->second->getName()->mString)] = attrib_it->second->getValue(); - } - item_entry["value"] = child->getTextContents(); - mFormData.append(item_entry); + LLUI::sSettingGroups["ignores"]->declareBOOL(name, show_notification, "Show notification with this name", TRUE); + mIgnoreSetting = LLUI::sSettingGroups["ignores"]->getControl(name); } + } + + LLParamSDParser parser; + parser.writeSD(mFormData, p.form_elements); + + mFormData = mFormData[""]; + if (!mFormData.isArray()) + { + // change existing contents to a one element array + LLSD new_llsd_array = LLSD::emptyArray(); + new_llsd_array.append(mFormData); + mFormData = new_llsd_array; + } - child = child->getNextSibling(); + for (LLSD::array_iterator it = mFormData.beginArray(), end_it = mFormData.endArray(); + it != end_it; + ++it) + { + // lift contents of form element up a level, since element type is already encoded in "type" param + if (it->isMap() && it->beginMap() != it->endMap()) + { + *it = it->beginMap()->second; + } } + + LL_DEBUGS("Notifications") << name << LL_ENDL; + LL_DEBUGS("Notifications") << ll_pretty_print_sd(mFormData) << LL_ENDL; } LLNotificationForm::LLNotificationForm(const LLSD& sd) @@ -293,16 +368,101 @@ std::string LLNotificationForm::getDefaultOption() return ""; } -LLNotificationTemplate::LLNotificationTemplate() : - mExpireSeconds(0), - mExpireOption(-1), - mURLOption(-1), - mURLOpenExternally(-1), - mPersist(false), - mUnique(false), - mPriority(NOTIFICATION_PRIORITY_NORMAL) +LLControlVariablePtr LLNotificationForm::getIgnoreSetting() +{ + return mIgnoreSetting; +} + +bool LLNotificationForm::getIgnored() { - mForm = LLNotificationFormPtr(new LLNotificationForm()); + bool show = true; + if (mIgnore != LLNotificationForm::IGNORE_NO + && mIgnoreSetting) + { + show = mIgnoreSetting->getValue().asBoolean(); + if (mInvertSetting) show = !show; + } + + return !show; +} + +void LLNotificationForm::setIgnored(bool ignored) +{ + if (mIgnoreSetting) + { + if (mInvertSetting) ignored = !ignored; + mIgnoreSetting->setValue(!ignored); + } +} + +LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Params& p) +: mName(p.name), + mType(p.type), + mMessage(p.value), + mLabel(p.label), + mIcon(p.icon), + mURL(p.url.value), + mExpireSeconds(p.duration), + mExpireOption(p.expire_option), + mURLOption(p.url.option), + mURLTarget(p.url.target), + mUnique(p.unique.isProvided()), + mPriority(p.priority), + mPersist(p.persist), + mDefaultFunctor(p.functor.isProvided() ? p.functor() : p.name()) +{ + if (p.sound.isProvided() + && LLUI::sSettingGroups["config"]->controlExists(p.sound)) + { + 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) + { + mUniqueContext.push_back(it->value); + } + + lldebugs << "notification \"" << mName << "\": tag count is " << p.tags.size() << llendl; + + for(LLInitParam::ParamIterator<LLNotificationTemplate::Tag>::const_iterator it = p.tags.begin(), + end_it = p.tags.end(); + it != end_it; + ++it) + { + lldebugs << " tag \"" << std::string(it->value) << "\"" << llendl; + mTags.push_back(it->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) : @@ -499,7 +659,7 @@ void LLNotification::respond(const LLSD& response) { mResponder->handleRespond(asLLSD(), response); } - else + else if (!mResponseFunctorName.empty()) { // look up the functor LLNotificationFunctorRegistry::ResponseFunctor functor = @@ -507,6 +667,11 @@ void LLNotification::respond(const LLSD& response) // and then call it functor(asLLSD(), response); } + else + { + // no registered responder + return; + } if (mTemporaryResponder && !isReusable()) { @@ -517,8 +682,7 @@ void LLNotification::respond(const LLSD& response) if (mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO) { - BOOL show_notification = mIgnored ? FALSE : TRUE; - LLUI::sSettingGroups["ignores"]->setBOOL(getName(), show_notification); + mForm->setIgnored(mIgnored); if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) { LLUI::sSettingGroups["ignores"]->setLLSD("Default" + getName(), response); @@ -528,6 +692,68 @@ void LLNotification::respond(const LLSD& response) update(); } +void LLNotification::respondWithDefault() +{ + respond(getResponseTemplate(WITH_DEFAULT_BUTTON)); +} + + +const std::string& LLNotification::getName() const +{ + return mTemplatep->mName; +} + +const std::string& LLNotification::getIcon() const +{ + return mTemplatep->mIcon; +} + + +bool LLNotification::isPersistent() const +{ + return mTemplatep->mPersist; +} + +std::string LLNotification::getType() const +{ + return (mTemplatep ? mTemplatep->mType : ""); +} + +S32 LLNotification::getURLOption() const +{ + return (mTemplatep ? mTemplatep->mURLOption : -1); +} + +S32 LLNotification::getURLOpenExternally() const +{ + return(mTemplatep? mTemplatep->mURLTarget == "_external": -1); +} + +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) { mIgnored = ignore; @@ -567,13 +793,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; } @@ -607,6 +839,8 @@ void LLNotification::init(const std::string& template_name, const LLSD& form_ele // apply substitution to form labels mForm->formatElements(mSubstitutions); + mIgnored = mForm->getIgnored(); + LLDate rightnow = LLDate::now(); if (mTemplatep->mExpireSeconds) { @@ -961,16 +1195,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()); } @@ -983,12 +1219,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) @@ -1001,7 +1241,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); } } @@ -1030,6 +1270,7 @@ LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelN void LLNotifications::initSingleton() { loadTemplates(); + loadVisibilityRules(); createDefaultChannels(); } @@ -1037,15 +1278,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 @@ -1053,30 +1298,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) { @@ -1095,11 +1332,6 @@ bool LLNotifications::templateExists(const std::string& name) return (mTemplates.count(name) != 0); } -void LLNotifications::clearTemplates() -{ - mTemplates.clear(); -} - void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option) { LLNotificationPtr temp_notify(new LLNotification(params)); @@ -1129,7 +1361,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) @@ -1143,13 +1374,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; } } } @@ -1162,197 +1392,147 @@ void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements) } } -// private to this file -// returns true if the template request was invalid and there's nothing else we -// can do with this node, false if you should keep processing (it may have -// replaced the contents of the node referred to) -LLXMLNodePtr LLNotifications::checkForXMLTemplate(LLXMLNodePtr item) +void replaceFormText(LLNotificationForm::Params& form, const std::string& pattern, const std::string& replace) { - if (item->hasName("usetemplate")) + if (form.ignore.isProvided() && form.ignore.text() == pattern) { - std::string replacementName; - if (item->getAttributeString("name", replacementName)) + 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) + { + if (it->button.isChosen() && it->button.text() == pattern) { - StringMap replacements; - for (LLXMLAttribList::const_iterator it=item->mAttributes.begin(); - it != item->mAttributes.end(); ++it) - { - replacements[it->second->getName()->mString] = it->second->getValue(); - } - if (mXmlTemplates.count(replacementName)) - { - item=LLXMLNode::replaceNode(item, mXmlTemplates[replacementName]); - - // walk the nodes looking for $(substitution) here and replace - replaceSubstitutionStrings(item, replacements); - } - else - { - llwarns << "XML template lookup failure on '" << replacementName << "' " << llendl; - } + it->button.text = replace; } } - return item; +} + +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"; - LLXMLNodePtr root; + std::vector<std::string> search_paths; - BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); + 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 = LLXMLNode::getLayeredXMLNode(root, search_paths); if (!success || root.isNull() || !root->hasName( "notifications" )) { - llerrs << "Problem reading UI Notifications file: " << xml_filename << llendl; + llerrs << "Problem reading UI Notifications file: " << base_filename << llendl; return false; } - - clearTemplates(); - - for (LLXMLNodePtr item = root->getFirstChild(); - item.notNull(); item = item->getNextSibling()) + + LLNotificationTemplate::Notifications params; + LLXUIParser parser; + parser.readXUI(root, params, base_filename); + + if(!params.validateBlock()) { - // we do this FIRST so that item can be changed if we - // encounter a usetemplate -- we just replace the - // current xml node and keep processing - item = checkForXMLTemplate(item); - - if (item->hasName("global")) - { - std::string global_name; - if (item->getAttributeString("name", global_name)) - { - mGlobalStrings[global_name] = item->getTextContents(); - } - continue; - } - - if (item->hasName("template")) - { - // store an xml template; templates must have a single node (can contain - // other nodes) - std::string name; - item->getAttributeString("name", name); - LLXMLNodePtr ptr = item->getFirstChild(); - mXmlTemplates[name] = ptr; - continue; - } - - if (!item->hasName("notification")) - { - llwarns << "Unexpected entity " << item->getName()->mString << - " found in " << xml_filename << llendl; - continue; - } - - // now we know we have a notification entry, so let's build it - LLNotificationTemplatePtr pTemplate(new LLNotificationTemplate()); + llerrs << "Problem reading UI Notifications file: " << base_filename << llendl; + return false; + } - if (!item->getAttributeString("name", pTemplate->mName)) - { - llwarns << "Unable to parse notification with no name" << llendl; - continue; - } - - //llinfos << "Parsing " << pTemplate->mName << llendl; - - pTemplate->mMessage = item->getTextContents(); - pTemplate->mDefaultFunctor = pTemplate->mName; - item->getAttributeString("type", pTemplate->mType); - item->getAttributeString("icon", pTemplate->mIcon); - item->getAttributeString("label", pTemplate->mLabel); - item->getAttributeU32("duration", pTemplate->mExpireSeconds); - item->getAttributeU32("expireOption", pTemplate->mExpireOption); - - std::string priority; - item->getAttributeString("priority", priority); - pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL; - if (!priority.empty()) - { - if (priority == "low") pTemplate->mPriority = NOTIFICATION_PRIORITY_LOW; - if (priority == "normal") pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL; - if (priority == "high") pTemplate->mPriority = NOTIFICATION_PRIORITY_HIGH; - if (priority == "critical") pTemplate->mPriority = NOTIFICATION_PRIORITY_CRITICAL; - } - - item->getAttributeString("functor", pTemplate->mDefaultFunctor); + mTemplates.clear(); - BOOL persist = false; - item->getAttributeBOOL("persist", persist); - pTemplate->mPersist = persist; - - std::string sound; - item->getAttributeString("sound", sound); - if (!sound.empty()) + for(LLInitParam::ParamIterator<LLNotificationTemplate::GlobalString>::const_iterator it = params.strings.begin(), end_it = params.strings.end(); + it != end_it; + ++it) + { + mGlobalStrings[it->name] = it->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) + { + form_templates[it->name] = it->form; + } + + for(LLInitParam::ParamIterator<LLNotificationTemplate::Params>::iterator it = params.notifications.begin(), end_it = params.notifications.end(); + it != end_it; + ++it) + { + if (it->form_ref.form_template.isChosen()) { - // test for bad sound effect name / missing effect - if (LLUI::sSettingGroups["config"]->controlExists(sound)) + // 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()) { - pTemplate->mSoundEffect = - LLUUID(LLUI::sSettingGroups["config"]->getString(sound)); + replaceFormText(it->form_ref.form, "$yestext", it->form_ref.form_template.yes_text); } - else + if(it->form_ref.form_template.no_text.isProvided()) { - llwarns << "Unknown sound effect control name " << sound - << llendl; + replaceFormText(it->form_ref.form, "$notext", it->form_ref.form_template.no_text); } - } - - for (LLXMLNodePtr child = item->getFirstChild(); - !child.isNull(); child = child->getNextSibling()) - { - child = checkForXMLTemplate(child); - - // <url> - if (child->hasName("url")) + if(it->form_ref.form_template.cancel_text.isProvided()) { - pTemplate->mURL = child->getTextContents(); - child->getAttributeU32("option", pTemplate->mURLOption); - child->getAttributeU32("openexternally", pTemplate->mURLOpenExternally); + replaceFormText(it->form_ref.form, "$canceltext", it->form_ref.form_template.cancel_text); } - - if (child->hasName("unique")) - { - pTemplate->mUnique = true; - for (LLXMLNodePtr formitem = child->getFirstChild(); - !formitem.isNull(); formitem = formitem->getNextSibling()) - { - if (formitem->hasName("context")) - { - std::string key; - formitem->getAttributeString("key", key); - pTemplate->mUniqueContext.push_back(key); - //llwarns << "adding " << key << " to unique context" << llendl; - } - else - { - llwarns << "'unique' has unrecognized subelement " - << formitem->getName()->mString << llendl; - } - } - } - - // <form> - if (child->hasName("form")) + if(it->form_ref.form_template.ignore_text.isProvided()) { - pTemplate->mForm = LLNotificationFormPtr(new LLNotificationForm(pTemplate->mName, child)); + replaceFormText(it->form_ref.form, "$ignoretext", it->form_ref.form_template.ignore_text); } } - addTemplate(pTemplate->mName, pTemplate); + mTemplates[it->name] = LLNotificationTemplatePtr(new LLNotificationTemplate(*it)); } - - //std::ostringstream ostream; - //root->writeToOstream(ostream, "\n "); - //llwarns << ostream.str() << 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(); + + for(LLInitParam::ParamIterator<LLNotificationVisibilityRule::Rule>::iterator it = params.rules.begin(), + end_it = params.rules.end(); + it != end_it; + ++it) + { + mVisibilityRules.push_back(LLNotificationVisibilityRulePtr(new LLNotificationVisibilityRule(*it))); + } + return true; } // 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, @@ -1396,6 +1576,8 @@ LLNotificationPtr LLNotifications::add(const LLNotification::Params& p) void LLNotifications::add(const LLNotificationPtr pNotif) { + if (pNotif == NULL) return; + // first see if we already have it -- if so, that's a problem LLNotificationSet::iterator it=mItems.find(pNotif); if (it != mItems.end()) @@ -1408,6 +1590,8 @@ void LLNotifications::add(const LLNotificationPtr pNotif) void LLNotifications::cancel(LLNotificationPtr pNotif) { + if (pNotif == NULL || pNotif->isCancelled()) return; + LLNotificationSet::iterator it=mItems.find(pNotif); if (it == mItems.end()) { @@ -1417,6 +1601,30 @@ void LLNotifications::cancel(LLNotificationPtr pNotif) updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); } +void LLNotifications::cancelByName(const std::string& name) +{ + std::vector<LLNotificationPtr> notifs_to_cancel; + for (LLNotificationSet::iterator it=mItems.begin(), end_it = mItems.end(); + it != end_it; + ++it) + { + LLNotificationPtr pNotif = *it; + if (pNotif->getName() == name) + { + notifs_to_cancel.push_back(pNotif); + } + } + + for (std::vector<LLNotificationPtr>::iterator it = notifs_to_cancel.begin(), end_it = notifs_to_cancel.end(); + it != end_it; + ++it) + { + LLNotificationPtr pNotif = *it; + pNotif->cancel(); + updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); + } +} + void LLNotifications::update(const LLNotificationPtr pNotif) { LLNotificationSet::iterator it=mItems.find(pNotif); @@ -1471,6 +1679,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 // ========================================================= @@ -1481,17 +1776,50 @@ std::ostream& operator<<(std::ostream& s, const LLNotification& notification) return s; } -void LLPostponedNotification::onCachedNameReceived(const LLUUID& id, const std::string& first, - const std::string& last, bool is_group) +//static +void LLPostponedNotification::lookupName(LLPostponedNotification* thiz, + const LLUUID& id, + bool is_group) { - mName = first + " " + last; + if (is_group) + { + gCacheName->getGroup(id, + boost::bind(&LLPostponedNotification::onGroupNameCache, + thiz, _1, _2, _3)); + } + else + { + LLAvatarNameCache::get(id, + boost::bind(&LLPostponedNotification::onAvatarNameCache, + thiz, _1, _2)); + } +} - LLStringUtil::trim(mName); - if (mName.empty()) +void LLPostponedNotification::onGroupNameCache(const LLUUID& id, + const std::string& full_name, + bool is_group) +{ + finalizeName(full_name); +} + +void LLPostponedNotification::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + std::string name = av_name.getCompleteName(); + + // from PE merge - we should figure out if this is the right thing to do + if (name.empty()) { - llwarns << "Empty name received for Id: " << id << llendl; - mName = SYSTEM_FROM; + llwarns << "Empty name received for Id: " << agent_id << llendl; + name = SYSTEM_FROM; } + + finalizeName(name); +} + +void LLPostponedNotification::finalizeName(const std::string& name) +{ + mName = name; modifyNotificationParams(); LLNotifications::instance().add(mParams); cleanup(); diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 2cc8803f10..0c4d4fc897 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -98,9 +98,8 @@ #include "llinitparam.h" #include "llnotificationslistener.h" #include "llnotificationptr.h" -#include "llcachename.h" - +class LLAvatarName; typedef enum e_notification_priority { NOTIFICATION_PRIORITY_UNSPECIFIED, @@ -110,6 +109,11 @@ typedef enum e_notification_priority NOTIFICATION_PRIORITY_CRITICAL } ENotificationPriority; +struct NotificationPriorityValues : public LLInitParam::TypeValuesHelper<ENotificationPriority, NotificationPriorityValues> +{ + static void declareValues(); +}; + class LLNotificationResponderInterface { public: @@ -157,6 +161,69 @@ class LLNotificationForm LOG_CLASS(LLNotificationForm); public: + struct FormElementBase : public LLInitParam::Block<FormElementBase> + { + Optional<std::string> name; + + FormElementBase(); + }; + + struct FormIgnore : public LLInitParam::Block<FormIgnore, FormElementBase> + { + Optional<std::string> text; + Optional<bool> save_option; + Optional<std::string> control; + Optional<bool> invert_control; + + FormIgnore(); + }; + + struct FormButton : public LLInitParam::Block<FormButton, FormElementBase> + { + Mandatory<S32> index; + Mandatory<std::string> text; + Optional<std::string> ignore; + Optional<bool> is_default; + + Mandatory<std::string> type; + + FormButton(); + }; + + struct FormInput : public LLInitParam::Block<FormInput, FormElementBase> + { + 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> + { + Alternative<FormButton> button; + Alternative<FormInput> input; + + FormElement(); + }; + + struct FormElements : public LLInitParam::Block<FormElements> + { + Multiple<FormElement> elements; + FormElements(); + }; + + struct Params : public LLInitParam::Block<Params> + { + Optional<std::string> name; + Optional<FormIgnore> ignore; + Optional<FormElements> form_elements; + + Params(); + }; + typedef enum e_ignore_type { IGNORE_NO, @@ -167,8 +234,7 @@ public: LLNotificationForm(); LLNotificationForm(const LLSD& sd); - LLNotificationForm(const std::string& name, - const LLPointer<class LLXMLNode> xml_node); + LLNotificationForm(const std::string& name, const Params& p); LLSD asLLSD() const; @@ -181,97 +247,35 @@ public: // appends form elements from another form serialized as LLSD void append(const LLSD& sub_form); std::string getDefaultOption(); + LLPointer<class LLControlVariable> getIgnoreSetting(); + bool getIgnored(); + void setIgnored(bool ignored); EIgnoreType getIgnoreType() { return mIgnore; } std::string getIgnoreMessage() { return mIgnoreMsg; } private: - LLSD mFormData; - EIgnoreType mIgnore; - std::string mIgnoreMsg; + LLSD mFormData; + EIgnoreType mIgnore; + std::string mIgnoreMsg; + LLPointer<class LLControlVariable> mIgnoreSetting; + bool mInvertSetting; }; typedef boost::shared_ptr<LLNotificationForm> LLNotificationFormPtr; -// This is the class of object read from the XML file (notifications.xml, -// from the appropriate local language directory). -struct LLNotificationTemplate -{ - LLNotificationTemplate(); - // the name of the notification -- the key used to identify it - // Ideally, the key should follow variable naming rules - // (no spaces or punctuation). - std::string mName; - // The type of the notification - // used to control which queue it's stored in - std::string mType; - // The text used to display the notification. Replaceable parameters - // are enclosed in square brackets like this []. - std::string mMessage; - // The label for the notification; used for - // certain classes of notification (those with a window and a window title). - // Also used when a notification pops up underneath the current one. - // Replaceable parameters can be used in the label. - std::string mLabel; - // The name of the icon image. This should include an extension. - std::string mIcon; - // This is the Highlander bit -- "There Can Be Only One" - // An outstanding notification with this bit set - // is updated by an incoming notification with the same name, - // rather than creating a new entry in the queue. - // (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 - // 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 - // sender but still differentiating different offers. Example: Invitation to - // conference chat. - std::vector<std::string> mUniqueContext; - // If this notification expires automatically, this value will be - // nonzero, and indicates the number of seconds for which the notification - // will be valid (a teleport offer, for example, might be valid for - // 300 seconds). - U32 mExpireSeconds; - // if the offer expires, one of the options is chosen automatically - // based on its "value" parameter. This controls which one. - // If expireSeconds is specified, expireOption should also be specified. - U32 mExpireOption; - // if the notification contains a url, it's stored here (and replaced - // into the message where [_URL] is found) - std::string mURL; - // if there's a URL in the message, this controls which option visits - // that URL. Obsolete this and eliminate the buttons for affected - // messages when we allow clickable URLs in the UI - U32 mURLOption; - - U32 mURLOpenExternally; - //This is a flag that tells if the url needs to open externally dispite - //what the user setting is. - - // does this notification persist across sessions? if so, it will be - // serialized to disk on first receipt and read on startup - bool mPersist; - // This is the name of the default functor, if present, to be - // used for the notification's callback. It is optional, and used only if - // the notification is constructed without an identified functor. - std::string mDefaultFunctor; - // The form data associated with a given notification (buttons, text boxes, etc) - LLNotificationFormPtr mForm; - // default priority for notifications of this type - ENotificationPriority mPriority; - // UUID of the audio file to be played when this notification arrives - // this is loaded as a name, but looked up to get the UUID upon template load. - // If null, it wasn't specified. - LLUUID mSoundEffect; -}; + +struct LLNotificationTemplate; // we want to keep a map of these by name, and it's best to manage them // 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 @@ -302,7 +306,7 @@ public: // optional Optional<LLSD> substitutions; Optional<LLSD> payload; - Optional<ENotificationPriority> priority; + Optional<ENotificationPriority, NotificationPriorityValues> priority; Optional<LLSD> form_elements; Optional<LLDate> time_stamp; Optional<LLNotificationContext*> context; @@ -445,6 +449,7 @@ public: LLSD asLLSD(); void respond(const LLSD& sd); + void respondWithDefault(); void* getResponder() { return mResponderObj; } @@ -462,6 +467,13 @@ public: return mRespondedTo; } + bool isActive() const + { + return !isRespondedTo() + && !isCancelled() + && !isExpired(); + } + const LLSD& getResponse() { return mResponse; } bool isIgnored() const @@ -469,15 +481,11 @@ public: return mIgnored; } - const std::string& getName() const - { - return mTemplatep->mName; - } + const std::string& getName() const; - bool isPersistent() const - { - return mTemplatep->mPersist; - } + const std::string& getIcon() const; + + bool isPersistent() const; const LLUUID& id() const { @@ -499,28 +507,12 @@ public: return mTimestamp; } - std::string getType() const - { - return (mTemplatep ? mTemplatep->mType : ""); - } - + std::string getType() const; std::string getMessage() const; std::string getLabel() const; - std::string getURL() const; -// { -// return (mTemplatep ? mTemplatep->mURL : ""); -// } - - S32 getURLOption() const - { - return (mTemplatep ? mTemplatep->mURLOption : -1); - } - - S32 getURLOpenExternally() const - { - return(mTemplatep? mTemplatep->mURLOpenExternally : -1); - } + S32 getURLOption() const; + S32 getURLOpenExternally() const; const LLNotificationFormPtr getForm(); @@ -590,7 +582,9 @@ public: std::string summarize() const; - bool hasUniquenessConstraints() const { return (mTemplatep ? mTemplatep->mUnique : false);} + bool hasUniquenessConstraints() const; + + bool matchesTag(const std::string& tag); virtual ~LLNotification() {} }; @@ -869,10 +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(); - LLPointer<class LLXMLNode> checkForXMLTemplate(LLPointer<class LLXMLNode> item); + bool loadVisibilityRules(); // Add a simple notification (from XUI) void addFromCallback(const LLSD& name); @@ -894,6 +892,7 @@ public: void add(const LLNotificationPtr pNotif); void cancel(LLNotificationPtr pNotif); + void cancelByName(const std::string& name); void update(const LLNotificationPtr pNotif); LLNotificationPtr find(LLUUID uuid); @@ -917,9 +916,9 @@ public: // test for existence bool templateExists(const std::string& name); - // useful if you're reloading the file - void clearTemplates(); // erase all templates + typedef std::list<LLNotificationVisibilityRulePtr> VisibilityRuleList; + void forceResponse(const LLNotification::Params& params, S32 option); void createDefaultChannels(); @@ -935,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(); @@ -950,15 +951,12 @@ private: LLNotificationChannelPtr pHistoryChannel; LLNotificationChannelPtr pExpirationChannel; - // put your template in - bool addTemplate(const std::string& name, LLNotificationTemplatePtr theTemplate); TemplateMap mTemplates; + VisibilityRuleList mVisibilityRules; + std::string mFileName; - typedef std::map<std::string, LLPointer<class LLXMLNode> > XMLTemplateMap; - XMLTemplateMap mXmlTemplates; - LLNotificationMap mUniqueNotifications; typedef std::map<std::string, std::string> GlobalStringMap; @@ -994,17 +992,20 @@ public: { // upcast T to the base type to restrict T derivation from LLPostponedNotification LLPostponedNotification* thiz = new T(); - thiz->mParams = params; - gCacheName->get(id, is_group, boost::bind( - &LLPostponedNotification::onCachedNameReceived, thiz, _1, _2, - _3, _4)); + // Avoid header file dependency on llcachename.h + lookupName(thiz, id, is_group); } private: - void onCachedNameReceived(const LLUUID& id, const std::string& first, - const std::string& last, bool is_group); + static void lookupName(LLPostponedNotification* thiz, const LLUUID& id, bool is_group); + // only used for groups + void onGroupNameCache(const LLUUID& id, const std::string& full_name, bool is_group); + // only used for avatars + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); + // used for both group and avatar names + void finalizeName(const std::string& name); void cleanup() { diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp index 44a90398fd..3bbeb3a778 100644 --- a/indra/llui/llnotificationslistener.cpp +++ b/indra/llui/llnotificationslistener.cpp @@ -29,6 +29,7 @@ #include "linden_common.h" #include "llnotificationslistener.h" #include "llnotifications.h" +#include "llnotificationtemplate.h" #include "llsd.h" #include "llui.h" @@ -182,7 +183,11 @@ void LLNotificationsListener::ignore(const LLSD& params) const if (params["name"].isDefined()) { // ["name"] was passed: ignore just that notification - LLUI::sSettingGroups["ignores"]->setBOOL(params["name"], ignore); + LLNotificationTemplatePtr templatep = mNotifications.getTemplate(params["name"]); + if (templatep) + { + templatep->mForm->setIgnored(ignore); + } } else { diff --git a/indra/llui/llnotificationtemplate.h b/indra/llui/llnotificationtemplate.h new file mode 100644 index 0000000000..eff572b553 --- /dev/null +++ b/indra/llui/llnotificationtemplate.h @@ -0,0 +1,297 @@ +/** +* @file llnotificationtemplate.h +* @brief Description of notification contents +* @author Q (with assistance from Richard and Coco) +* +* $LicenseInfo:firstyear=2008&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_TEMPLATE_H +#define LL_LLNOTIFICATION_TEMPLATE_H + +//#include <string> +//#include <list> +//#include <vector> +//#include <map> +//#include <set> +//#include <iomanip> +//#include <sstream> +// +//#include <boost/utility.hpp> +//#include <boost/shared_ptr.hpp> +//#include <boost/enable_shared_from_this.hpp> +//#include <boost/type_traits.hpp> +// +//// we want to minimize external dependencies, but this one is important +//#include "llsd.h" +// +//// and we need this to manage the notification callbacks +//#include "llevents.h" +//#include "llfunctorregistry.h" +//#include "llpointer.h" +#include "llinitparam.h" +//#include "llnotificationslistener.h" +//#include "llnotificationptr.h" +//#include "llcachename.h" +#include "llnotifications.h" + + +typedef boost::shared_ptr<LLNotificationForm> LLNotificationFormPtr; + +// This is the class of object read from the XML file (notifications.xml, +// from the appropriate local language directory). +struct LLNotificationTemplate +{ + struct GlobalString : public LLInitParam::Block<GlobalString> + { + Mandatory<std::string> name, + value; + + GlobalString() + : name("name"), + value("value") + {} + }; + + struct UniquenessContext : public LLInitParam::Block<UniquenessContext> + { + Mandatory<std::string> value; + + UniquenessContext() + : value("value") + { + addSynonym(value, "key"); + } + + }; + + struct UniquenessConstraint : public LLInitParam::Block<UniquenessConstraint> + { + private: + // this idiom allows + // <notification unique="true"> + // as well as + // <notification> <unique> <context></context> </unique>... + Optional<bool> dummy_val; + public: + Multiple<UniquenessContext> contexts; + + UniquenessConstraint() + : contexts("context"), + dummy_val("") + {} + }; + + // Templates are used to define common form types, such as OK/Cancel dialogs, etc. + + struct Template : public LLInitParam::Block<Template> + { + Mandatory<std::string> name; + Mandatory<LLNotificationForm::Params> form; + + Template() + : name("name"), + form("form") + {} + }; + + // Reference a template to use its form elements + struct TemplateRef : public LLInitParam::Block<TemplateRef> + { + Mandatory<std::string> name; + Optional<std::string> yes_text, + no_text, + cancel_text, + ignore_text; + + TemplateRef() + : name("name"), + yes_text("yestext"), + no_text("notext"), + cancel_text("canceltext"), + ignore_text("ignoretext") + {} + }; + + struct URL : public LLInitParam::Block<URL> + { + Mandatory<S32> option; + Mandatory<std::string> value; + Optional<std::string> target; + Ignored name; + + URL() + : option("option", -1), + value("value"), + target("target", "_blank"), + name("name") + {} + }; + + struct FormRef : public LLInitParam::Choice<FormRef> + { + Alternative<LLNotificationForm::Params> form; + Alternative<TemplateRef> form_template; + + FormRef() + : form("form"), + form_template("usetemplate") + {} + }; + + struct Tag : public LLInitParam::Block<Tag> + { + Mandatory<std::string> value; + + Tag() + : value("value") + {} + }; + + struct Params : public LLInitParam::Block<Params> + { + Mandatory<std::string> name; + Optional<bool> persist; + Optional<std::string> functor, + icon, + label, + sound, + type, + value; + Optional<U32> duration; + Optional<S32> expire_option; + Optional<URL> url; + Optional<UniquenessConstraint> unique; + Optional<FormRef> form_ref; + Optional<ENotificationPriority, + NotificationPriorityValues> priority; + Multiple<Tag> tags; + + + Params() + : name("name"), + persist("persist", false), + functor("functor"), + icon("icon"), + label("label"), + priority("priority"), + sound("sound"), + type("type"), + value("value"), + duration("duration"), + expire_option("expireOption", -1), + url("url"), + unique("unique"), + form_ref(""), + tags("tag") + {} + + }; + + struct Notifications : public LLInitParam::Block<Notifications> + { + Multiple<GlobalString> strings; + Multiple<Template> templates; + Multiple<Params> notifications; + + Notifications() + : strings("global"), + notifications("notification"), + templates("template") + {} + }; + + LLNotificationTemplate(const Params& p); + // the name of the notification -- the key used to identify it + // Ideally, the key should follow variable naming rules + // (no spaces or punctuation). + std::string mName; + // The type of the notification + // used to control which queue it's stored in + std::string mType; + // The text used to display the notification. Replaceable parameters + // are enclosed in square brackets like this []. + std::string mMessage; + // The label for the notification; used for + // certain classes of notification (those with a window and a window title). + // Also used when a notification pops up underneath the current one. + // Replaceable parameters can be used in the label. + std::string mLabel; + // The name of the icon image. This should include an extension. + std::string mIcon; + // This is the Highlander bit -- "There Can Be Only One" + // An outstanding notification with this bit set + // is updated by an incoming notification with the same name, + // rather than creating a new entry in the queue. + // (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 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 + // sender but still differentiating different offers. Example: Invitation to + // conference chat. + std::vector<std::string> mUniqueContext; + // If this notification expires automatically, this value will be + // nonzero, and indicates the number of seconds for which the notification + // will be valid (a teleport offer, for example, might be valid for + // 300 seconds). + U32 mExpireSeconds; + // if the offer expires, one of the options is chosen automatically + // based on its "value" parameter. This controls which one. + // If expireSeconds is specified, expireOption should also be specified. + U32 mExpireOption; + // if the notification contains a url, it's stored here (and replaced + // into the message where [_URL] is found) + std::string mURL; + // if there's a URL in the message, this controls which option visits + // that URL. Obsolete this and eliminate the buttons for affected + // messages when we allow clickable URLs in the UI + U32 mURLOption; + + std::string mURLTarget; + //This is a flag that tells if the url needs to open externally dispite + //what the user setting is. + + // does this notification persist across sessions? if so, it will be + // serialized to disk on first receipt and read on startup + bool mPersist; + // This is the name of the default functor, if present, to be + // used for the notification's callback. It is optional, and used only if + // the notification is constructed without an identified functor. + std::string mDefaultFunctor; + // The form data associated with a given notification (buttons, text boxes, etc) + LLNotificationFormPtr mForm; + // default priority for notifications of this type + ENotificationPriority mPriority; + // UUID of the audio file to be played when this notification arrives + // 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..78bdec2a8f --- /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::Choice<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 b2e08c48c5..b2383106a8 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -35,6 +35,7 @@ #include "llfontgl.h" #include "llrect.h" #include "llerror.h" +#include "lldir.h" #include "lltimer.h" #include "llaccordionctrltab.h" @@ -52,6 +53,8 @@ #include "lltabcontainer.h" static LLDefaultChildRegistry::Register<LLPanel> r1("panel", &LLPanel::fromXML); +LLPanel::factory_stack_t LLPanel::sFactoryStack; + // Compiler optimization, generate extern template template class LLPanel* LLView::getChild<class LLPanel>( @@ -191,6 +194,8 @@ void LLPanel::draw() // draw background if( mBgVisible ) { + alpha = getCurrentTransparency(); + LLRect local_rect = getLocalRect(); if (mBgOpaque ) { @@ -380,8 +385,7 @@ LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLXMLNodePtr output_ LLPanel* panelp = NULL; - { - LLFastTimer timer(FTM_PANEL_CONSTRUCTION); + { LLFastTimer _(FTM_PANEL_CONSTRUCTION); if(!class_attr.empty()) { @@ -394,7 +398,7 @@ LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLXMLNodePtr output_ if (!panelp) { - panelp = LLUICtrlFactory::getInstance()->createFactoryPanel(name); + panelp = createFactoryPanel(name); llassert(panelp); if (!panelp) @@ -407,20 +411,20 @@ LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLXMLNodePtr output_ // factory panels may have registered their own factory maps if (!panelp->getFactoryMap().empty()) { - LLUICtrlFactory::instance().pushFactoryFunctions(&panelp->getFactoryMap()); + sFactoryStack.push_back(&panelp->getFactoryMap()); } // for local registry callbacks; define in constructor, referenced in XUI or postBuild panelp->mCommitCallbackRegistrar.pushScope(); panelp->mEnableCallbackRegistrar.pushScope(); - panelp->initPanelXML(node, parent, output_node); + panelp->initPanelXML(node, parent, output_node, LLUICtrlFactory::getDefaultParams<LLPanel>()); panelp->mCommitCallbackRegistrar.popScope(); panelp->mEnableCallbackRegistrar.popScope(); if (!panelp->getFactoryMap().empty()) { - LLUICtrlFactory::instance().popFactoryFunctions(); + sFactoryStack.pop_back(); } return panelp; @@ -432,7 +436,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 @@ -444,8 +448,8 @@ void LLPanel::initFromParams(const LLPanel::Params& p) setVisibleCallback(initCommitCallback(p.visible_callback)); } - for (LLInitParam::ParamIterator<LocalizedString>::const_iterator it = p.strings().begin(); - it != p.strings().end(); + for (LLInitParam::ParamIterator<LocalizedString>::const_iterator it = p.strings.begin(); + it != p.strings.end(); ++it) { mUIStrings[it->name] = it->value; @@ -487,11 +491,9 @@ static LLFastTimer::DeclareTimer FTM_PANEL_SETUP("Panel Setup"); static LLFastTimer::DeclareTimer FTM_EXTERNAL_PANEL_LOAD("Load Extern Panel Reference"); static LLFastTimer::DeclareTimer FTM_PANEL_POSTBUILD("Panel PostBuild"); -BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node) +BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node, const LLPanel::Params& default_params) { - const LLPanel::Params& default_params(LLUICtrlFactory::getDefaultParams<LLPanel>()); Params params(default_params); - { LLFastTimer timer(FTM_PANEL_SETUP); @@ -505,6 +507,8 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu setXMLFilename(xml_filename); } + LLXUIParser parser; + if (!xml_filename.empty()) { LLUICtrlFactory::instance().pushFileName(xml_filename); @@ -514,12 +518,11 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu { //if we are exporting, we want to export the current xml //not the referenced xml - LLXUIParser::instance().readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); Params output_params(params); setupParamsForExport(output_params, parent); output_node->setName(node->getName()->mString); - LLXUIParser::instance().writeXUI( - output_node, output_params, &default_params); + parser.writeXUI(output_node, output_params, &default_params); return TRUE; } @@ -530,7 +533,7 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu return FALSE; } - LLXUIParser::instance().readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName()); // add children using dimensions from referenced xml for consistent layout setShape(params.rect); @@ -540,15 +543,14 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu } // ask LLUICtrlFactory for filename, since xml_filename might be empty - LLXUIParser::instance().readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); if (output_node) { Params output_params(params); setupParamsForExport(output_params, parent); output_node->setName(node->getName()->mString); - LLXUIParser::instance().writeXUI( - output_node, output_params, &default_params); + parser.writeXUI(output_node, output_params, &default_params); } params.from_xui = true; @@ -904,7 +906,7 @@ LLPanel *LLPanel::childGetVisiblePanelWithHelp() child = *it; // do we have a panel with a help topic? LLPanel *panel = dynamic_cast<LLPanel *>(child); - if (panel && panel->getVisible() && !panel->getHelpTopic().empty()) + if (panel && panel->isInVisibleChain() && !panel->getHelpTopic().empty()) { return panel; } @@ -959,3 +961,89 @@ boost::signals2::connection LLPanel::setVisibleCallback( const commit_signal_t:: return mVisibleSignal->connect(cb); } + +static LLFastTimer::DeclareTimer FTM_BUILD_PANELS("Build Panels"); + +//----------------------------------------------------------------------------- +// buildPanel() +//----------------------------------------------------------------------------- +BOOL LLPanel::buildFromFile(const std::string& filename, LLXMLNodePtr output_node, const LLPanel::Params& default_params) +{ + LLFastTimer timer(FTM_BUILD_PANELS); + BOOL didPost = FALSE; + LLXMLNodePtr root; + + //if exporting, only load the language being exported, + //instead of layering localized version on top of english + if (output_node) + { + if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root)) + { + llwarns << "Couldn't parse panel from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; + return didPost; + } + } + else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + llwarns << "Couldn't parse panel from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; + return didPost; + } + + // root must be called panel + if( !root->hasName("panel" ) ) + { + llwarns << "Root node should be named panel in : " << filename << llendl; + return didPost; + } + + lldebugs << "Building panel " << filename << llendl; + + LLUICtrlFactory::instance().pushFileName(filename); + { + if (!getFactoryMap().empty()) + { + sFactoryStack.push_back(&getFactoryMap()); + } + + // for local registry callbacks; define in constructor, referenced in XUI or postBuild + getCommitCallbackRegistrar().pushScope(); + getEnableCallbackRegistrar().pushScope(); + + didPost = initPanelXML(root, NULL, output_node, default_params); + + getCommitCallbackRegistrar().popScope(); + getEnableCallbackRegistrar().popScope(); + + setXMLFilename(filename); + + if (!getFactoryMap().empty()) + { + sFactoryStack.pop_back(); + } + } + LLUICtrlFactory::instance().popFileName(); + return didPost; +} + +//----------------------------------------------------------------------------- +// createFactoryPanel() +//----------------------------------------------------------------------------- +LLPanel* LLPanel::createFactoryPanel(const std::string& name) +{ + std::deque<const LLCallbackMap::map_t*>::iterator itor; + for (itor = sFactoryStack.begin(); itor != sFactoryStack.end(); ++itor) + { + const LLCallbackMap::map_t* factory_map = *itor; + + // Look up this panel's name in the map. + LLCallbackMap::map_const_iter_t iter = factory_map->find( name ); + if (iter != factory_map->end()) + { + // Use the factory to create the panel, instead of using a default LLPanel. + LLPanel *ret = (LLPanel*) iter->second.mCallback( iter->second.mData ); + return ret; + } + } + LLPanel::Params panel_p; + return LLUICtrlFactory::create<LLPanel>(panel_p); +} diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index a7224648c1..7bbbeaf709 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -32,6 +32,7 @@ #include "llcallbackmap.h" #include "lluictrl.h" #include "llviewborder.h" +#include "lluiimage.h" #include "lluistring.h" #include "v4color.h" #include <list> @@ -104,7 +105,10 @@ protected: LLPanel(const LLPanel::Params& params = getDefaultParams()); public: -// LLPanel(const std::string& name, const LLRect& rect = LLRect(), BOOL bordered = TRUE); + BOOL buildFromFile(const std::string &filename, LLXMLNodePtr output_node = NULL, const LLPanel::Params&default_params = getDefaultParams()); + + static LLPanel* createFactoryPanel(const std::string& name); + /*virtual*/ ~LLPanel(); // LLView interface @@ -157,7 +161,7 @@ public: EnableCallbackRegistry::ScopedRegistrar& getEnableCallbackRegistrar() { return mEnableCallbackRegistrar; } void initFromParams(const Params& p); - BOOL initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); + BOOL initPanelXML( LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node, const LLPanel::Params& default_params); bool hasString(const std::string& name); std::string getString(const std::string& name, const LLStringUtil::format_map_t& args) const; @@ -256,6 +260,8 @@ protected: commit_signal_t* mVisibleSignal; // Called when visibility changes, passes new visibility as LLSD() 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; private: BOOL mBgVisible; // any background at all? @@ -285,4 +291,57 @@ extern template class LLPanel* LLView::getChild<class LLPanel>( const std::string& name, BOOL recurse) const; #endif +typedef boost::function<LLPanel* (void)> LLPanelClassCreatorFunc; + +// local static instance for registering a particular panel class + +class LLRegisterPanelClass +: public LLSingleton< LLRegisterPanelClass > +{ +public: + // reigister with either the provided builder, or the generic templated builder + void addPanelClass(const std::string& tag,LLPanelClassCreatorFunc func) + { + mPanelClassesNames[tag] = func; + } + + LLPanel* createPanelClass(const std::string& tag) + { + param_name_map_t::iterator iT = mPanelClassesNames.find(tag); + if(iT == mPanelClassesNames.end()) + return 0; + return iT->second(); + } + template<typename T> + static T* defaultPanelClassBuilder() + { + T* pT = new T(); + return pT; + } + +private: + typedef std::map< std::string, LLPanelClassCreatorFunc> param_name_map_t; + + param_name_map_t mPanelClassesNames; +}; + + +// local static instance for registering a particular panel class +template<typename T> +class LLRegisterPanelClassWrapper +: public LLRegisterPanelClass +{ +public: + // reigister with either the provided builder, or the generic templated builder + LLRegisterPanelClassWrapper(const std::string& tag); +}; + + +template<typename T> +LLRegisterPanelClassWrapper<T>::LLRegisterPanelClassWrapper(const std::string& tag) +{ + LLRegisterPanelClass::instance().addPanelClass(tag,&LLRegisterPanelClass::defaultPanelClassBuilder<T>); +} + + #endif 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 83c42a5ab8..3a12debf7e 100644 --- a/indra/llui/llradiogroup.cpp +++ b/indra/llui/llradiogroup.cpp @@ -69,7 +69,7 @@ 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"); @@ -85,23 +85,13 @@ 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) { - for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items().begin(); - it != p.items().end(); + for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items.begin(); + it != p.items.end(); ++it) { LLRadioGroup::ItemParams item_params(*it); @@ -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/llrngwriter.cpp b/indra/llui/llrngwriter.cpp index 50b7bbab90..5e6840d7df 100644 --- a/indra/llui/llrngwriter.cpp +++ b/indra/llui/llrngwriter.cpp @@ -30,10 +30,15 @@ #include "lluicolor.h" #include "lluictrlfactory.h" +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; + // // LLRNGWriter - writes Relax NG schema files based on a param block // LLRNGWriter::LLRNGWriter() +: Parser(sReadFuncs, sWriteFuncs, sInspectFuncs) { // register various callbacks for inspecting the contents of a param block registerInspectFunc<bool>(boost::bind(&LLRNGWriter::writeAttribute, this, "boolean", _1, _2, _3, _4)); diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index 3146418a7d..380c477eb2 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -422,9 +422,10 @@ void LLScrollContainer::draw() // Draw background if( mIsOpaque ) { + F32 alpha = getCurrentTransparency(); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4fv( mBackgroundColor.get().mV ); - gl_rect_2d( mInnerRect ); + gl_rect_2d(mInnerRect, mBackgroundColor.get() % alpha); } // Draw mScrolledViews and update scroll bars. diff --git a/indra/llui/llscrolllistcolumn.cpp b/indra/llui/llscrolllistcolumn.cpp index 2a4c1ca44c..696e4a2bb1 100644 --- a/indra/llui/llscrolllistcolumn.cpp +++ b/indra/llui/llscrolllistcolumn.cpp @@ -83,7 +83,14 @@ void LLScrollColumnHeader::draw() && (sort_column == mColumn->mSortingColumn || sort_column == mColumn->mName); BOOL is_ascending = mColumn->mParentCtrl->getSortAscending(); - setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, draw_arrow ? LLColor4::white : LLColor4::transparent); + if (draw_arrow) + { + setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, LLColor4::white); + } + else + { + setImageOverlay(LLUUID::null); + } // Draw children LLButton::draw(); diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 84e438cfb7..b7848ec37c 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -259,15 +259,15 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) } - for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator row_it = p.contents.columns().begin(); - row_it != p.contents.columns().end(); + for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator row_it = p.contents.columns.begin(); + row_it != p.contents.columns.end(); ++row_it) { addColumn(*row_it); } - for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = p.contents.rows().begin(); - row_it != p.contents.rows().end(); + for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = p.contents.rows.begin(); + row_it != p.contents.rows.end(); ++row_it) { addRow(*row_it); @@ -322,6 +322,7 @@ LLScrollListCtrl::~LLScrollListCtrl() delete mSortCallback; std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer()); } @@ -537,23 +538,7 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL r setNeedsSort(); break; - case ADD_SORTED: - { - // sort by column 0, in ascending order - std::vector<sort_column_t> single_sort_column; - single_sort_column.push_back(std::make_pair(0, TRUE)); - - mItemList.push_back(item); - std::stable_sort( - mItemList.begin(), - mItemList.end(), - SortScrollListItem(single_sort_column,mSortCallback)); - - // ADD_SORTED just sorts by first column... - // this might not match user sort criteria, so flag list as being in unsorted state - setNeedsSort(); - break; - } + case ADD_DEFAULT: case ADD_BOTTOM: mItemList.push_back(item); setNeedsSort(); @@ -1498,8 +1483,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) @@ -2386,10 +2372,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); } } @@ -2435,11 +2421,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; } } @@ -2578,7 +2564,8 @@ BOOL LLScrollListCtrl::canDeselect() const void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) { LLScrollListColumn::Params p; - LLParamSDParser::instance().readSD(column, p); + LLParamSDParser parser; + parser.readSD(column, p); addColumn(p, pos); } @@ -2596,8 +2583,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 @@ -2619,14 +2606,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; } } @@ -2682,8 +2669,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 @@ -2716,16 +2703,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; @@ -2759,36 +2747,40 @@ 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; } - +LLFastTimer::DeclareTimer FTM_ADD_SCROLLLIST_ELEMENT("Add Scroll List Item"); LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata) { + LLFastTimer _(FTM_ADD_SCROLLLIST_ELEMENT); LLScrollListItem::Params item_params; - LLParamSDParser::instance().readSD(element, item_params); + LLParamSDParser parser; + parser.readSD(element, item_params); item_params.userdata = userdata; return addRow(item_params, pos); } LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_p, EAddPosition pos) { + LLFastTimer _(FTM_ADD_SCROLLLIST_ELEMENT); LLScrollListItem *new_item = new LLScrollListItem(item_p); return addRow(new_item, item_p, pos); } LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos) { + LLFastTimer _(FTM_ADD_SCROLLLIST_ELEMENT); if (!item_p.validateBlock() || !new_item) return NULL; new_item->setNumColumns(mColumns.size()); // Add any columns we don't already have S32 col_index = 0; - for(LLInitParam::ParamIterator<LLScrollListCell::Params>::const_iterator itor = item_p.columns().begin(); - itor != item_p.columns().end(); + for(LLInitParam::ParamIterator<LLScrollListCell::Params>::const_iterator itor = item_p.columns.begin(); + itor != item_p.columns.end(); ++itor) { LLScrollListCell::Params cell_p = *itor; @@ -2816,7 +2808,7 @@ 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()); } @@ -2839,7 +2831,7 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS col_index++; } - if (item_p.columns().empty()) + if (item_p.columns.empty()) { if (mColumns.empty()) { @@ -2853,7 +2845,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 @@ -2868,10 +2860,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..09ab89960d 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -491,7 +491,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/llsdparam.cpp b/indra/llui/llsdparam.cpp index 54c3060c4a..9ad13054cb 100644 --- a/indra/llui/llsdparam.cpp +++ b/indra/llui/llsdparam.cpp @@ -29,55 +29,61 @@ // Project includes #include "llsdparam.h" +#include "llsdutil.h" + +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; // // LLParamSDParser // LLParamSDParser::LLParamSDParser() +: Parser(sReadFuncs, sWriteFuncs, sInspectFuncs) { using boost::bind; - registerParserFuncs<S32>(bind(&LLParamSDParser::readTypedValue<S32>, this, _1, &LLSD::asInteger), - bind(&LLParamSDParser::writeTypedValue<S32>, this, _1, _2)); - registerParserFuncs<U32>(bind(&LLParamSDParser::readTypedValue<U32>, this, _1, &LLSD::asInteger), - bind(&LLParamSDParser::writeU32Param, this, _1, _2)); - registerParserFuncs<F32>(bind(&LLParamSDParser::readTypedValue<F32>, this, _1, &LLSD::asReal), - bind(&LLParamSDParser::writeTypedValue<F32>, this, _1, _2)); - registerParserFuncs<F64>(bind(&LLParamSDParser::readTypedValue<F64>, this, _1, &LLSD::asReal), - bind(&LLParamSDParser::writeTypedValue<F64>, this, _1, _2)); - registerParserFuncs<bool>(bind(&LLParamSDParser::readTypedValue<F32>, this, _1, &LLSD::asBoolean), - bind(&LLParamSDParser::writeTypedValue<F32>, this, _1, _2)); - registerParserFuncs<std::string>(bind(&LLParamSDParser::readTypedValue<std::string>, this, _1, &LLSD::asString), - bind(&LLParamSDParser::writeTypedValue<std::string>, this, _1, _2)); - registerParserFuncs<LLUUID>(bind(&LLParamSDParser::readTypedValue<LLUUID>, this, _1, &LLSD::asUUID), - bind(&LLParamSDParser::writeTypedValue<LLUUID>, this, _1, _2)); - registerParserFuncs<LLDate>(bind(&LLParamSDParser::readTypedValue<LLDate>, this, _1, &LLSD::asDate), - bind(&LLParamSDParser::writeTypedValue<LLDate>, this, _1, _2)); - registerParserFuncs<LLURI>(bind(&LLParamSDParser::readTypedValue<LLURI>, this, _1, &LLSD::asURI), - bind(&LLParamSDParser::writeTypedValue<LLURI>, this, _1, _2)); - registerParserFuncs<LLSD>(bind(&LLParamSDParser::readSDParam, this, _1), - bind(&LLParamSDParser::writeTypedValue<LLSD>, this, _1, _2)); -} - -bool LLParamSDParser::readSDParam(void* value_ptr) -{ - if (!mCurReadSD) return false; - *((LLSD*)value_ptr) = *mCurReadSD; - return true; + if (sReadFuncs.empty()) + { + registerParserFuncs<LLInitParam::NoParamValue>(readNoValue, &LLParamSDParser::writeNoValue); + registerParserFuncs<S32>(readS32, &LLParamSDParser::writeTypedValue<S32>); + registerParserFuncs<U32>(readU32, &LLParamSDParser::writeU32Param); + registerParserFuncs<F32>(readF32, &LLParamSDParser::writeTypedValue<F32>); + registerParserFuncs<F64>(readF64, &LLParamSDParser::writeTypedValue<F64>); + registerParserFuncs<bool>(readBool, &LLParamSDParser::writeTypedValue<bool>); + registerParserFuncs<std::string>(readString, &LLParamSDParser::writeTypedValue<std::string>); + registerParserFuncs<LLUUID>(readUUID, &LLParamSDParser::writeTypedValue<LLUUID>); + registerParserFuncs<LLDate>(readDate, &LLParamSDParser::writeTypedValue<LLDate>); + registerParserFuncs<LLURI>(readURI, &LLParamSDParser::writeTypedValue<LLURI>); + registerParserFuncs<LLSD>(readSD, &LLParamSDParser::writeTypedValue<LLSD>); + } } // special case handling of U32 due to ambiguous LLSD::assign overload -bool LLParamSDParser::writeU32Param(const void* val_ptr, const parser_t::name_stack_t& name_stack) +bool LLParamSDParser::writeU32Param(LLParamSDParser::parser_t& parser, const void* val_ptr, const parser_t::name_stack_t& name_stack) { - if (!mWriteSD) return false; + LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser); + if (!sdparser.mWriteRootSD) return false; - LLSD* sd_to_write = getSDWriteNode(name_stack); + LLSD* sd_to_write = sdparser.getSDWriteNode(name_stack); if (!sd_to_write) return false; sd_to_write->assign((S32)*((const U32*)val_ptr)); return true; } +bool LLParamSDParser::writeNoValue(LLParamSDParser::parser_t& parser, const void* val_ptr, const 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; + + return true; +} + + void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent) { mCurReadSD = NULL; @@ -89,10 +95,13 @@ void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool void LLParamSDParser::writeSD(LLSD& sd, const LLInitParam::BaseBlock& block) { - mWriteSD = &sd; + mNameStack.clear(); + mWriteRootSD = &sd; block.serializeBlock(*this); } +const LLSD NO_VALUE_MARKER; + void LLParamSDParser::readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block) { if (sd.isMap()) @@ -116,6 +125,11 @@ void LLParamSDParser::readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block readSDValues(*it, block); } } + else if (sd.isUndefined()) + { + mCurReadSD = &NO_VALUE_MARKER; + block.submitValue(mNameStack, *this); + } else { mCurReadSD = &sd; @@ -139,6 +153,162 @@ void LLParamSDParser::readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block LLSD* LLParamSDParser::getSDWriteNode(const parser_t::name_stack_t& name_stack) { //TODO: implement nested LLSD writing - return mWriteSD; + 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::readNoValue(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); + + *((S32*)val_ptr) = self.mCurReadSD->asInteger(); + return true; +} + +bool LLParamSDParser::readU32(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((U32*)val_ptr) = self.mCurReadSD->asInteger(); + return true; +} + +bool LLParamSDParser::readF32(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((F32*)val_ptr) = self.mCurReadSD->asReal(); + return true; +} + +bool LLParamSDParser::readF64(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((F64*)val_ptr) = self.mCurReadSD->asReal(); + return true; +} + +bool LLParamSDParser::readBool(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((bool*)val_ptr) = self.mCurReadSD->asBoolean(); + return true; } +bool LLParamSDParser::readString(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((std::string*)val_ptr) = self.mCurReadSD->asString(); + return true; +} + +bool LLParamSDParser::readUUID(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((LLUUID*)val_ptr) = self.mCurReadSD->asUUID(); + return true; +} + +bool LLParamSDParser::readDate(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((LLDate*)val_ptr) = self.mCurReadSD->asDate(); + return true; +} + +bool LLParamSDParser::readURI(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((LLURI*)val_ptr) = self.mCurReadSD->asURI(); + return true; +} + +bool LLParamSDParser::readSD(Parser& parser, void* val_ptr) +{ + LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + + *((LLSD*)val_ptr) = *self.mCurReadSD; + return true; +} diff --git a/indra/llui/llsdparam.h b/indra/llui/llsdparam.h index e028fc30c2..69dab2b411 100644 --- a/indra/llui/llsdparam.h +++ b/indra/llui/llsdparam.h @@ -31,17 +31,14 @@ #include "llinitparam.h" class LLParamSDParser -: public LLInitParam::Parser, - public LLSingleton<LLParamSDParser> +: public LLInitParam::Parser { LOG_CLASS(LLParamSDParser); typedef LLInitParam::Parser parser_t; -protected: - LLParamSDParser(); - friend class LLSingleton<LLParamSDParser>; public: + LLParamSDParser(); void readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent = false); void writeSD(LLSD& sd, const LLInitParam::BaseBlock& block); @@ -51,20 +48,12 @@ private: void readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block); template<typename T> - bool readTypedValue(void* val_ptr, boost::function<T(const LLSD&)> parser_func) - { - if (!mCurReadSD) return false; - - *((T*)val_ptr) = parser_func(*mCurReadSD); - return true; - } - - template<typename T> - bool writeTypedValue(const void* val_ptr, const parser_t::name_stack_t& name_stack) + static bool writeTypedValue(Parser& parser, const void* val_ptr, const parser_t::name_stack_t& name_stack) { - if (!mWriteSD) return false; + LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser); + if (!sdparser.mWriteRootSD) return false; - LLSD* sd_to_write = getSDWriteNode(name_stack); + LLSD* sd_to_write = sdparser.getSDWriteNode(name_stack); if (!sd_to_write) return false; sd_to_write->assign(*((const T*)val_ptr)); @@ -73,12 +62,25 @@ private: LLSD* getSDWriteNode(const parser_t::name_stack_t& name_stack); - bool readSDParam(void* value_ptr); - bool writeU32Param(const void* value_ptr, 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 writeNoValue(Parser& parser, const void* value_ptr, const parser_t::name_stack_t& name_stack); + + static bool readNoValue(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); + static bool readF64(Parser& parser, void* val_ptr); + static bool readBool(Parser& parser, void* val_ptr); + static bool readString(Parser& parser, void* val_ptr); + static bool readUUID(Parser& parser, void* val_ptr); + static bool readDate(Parser& parser, void* val_ptr); + static bool readURI(Parser& parser, void* val_ptr); + static bool readSD(Parser& parser, void* val_ptr); Parser::name_stack_t mNameStack; const LLSD* mCurReadSD; - LLSD* mWriteSD; + LLSD* mWriteRootSD; + LLSD* mCurWriteSD; }; template<typename T> @@ -88,7 +90,8 @@ class LLSDParamAdapter : public T LLSDParamAdapter() {} LLSDParamAdapter(const LLSD& sd) { - LLParamSDParser::instance().readSD(sd, *this); + LLParamSDParser parser; + parser.readSD(sd, *this); } LLSDParamAdapter(const T& val) 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/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index 9decfa0b25..6b4e9cf923 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -119,7 +119,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) { params.font(p.font); } - params.max_length_bytes(MAX_STRING_LENGTH); + params.max_length.bytes(MAX_STRING_LENGTH); params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2))); if( mPrecision>0 )//should accept float numbers diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp index 5e09cee78b..bb731f4f7e 100644 --- a/indra/llui/llstyle.cpp +++ b/indra/llui/llstyle.cpp @@ -40,7 +40,8 @@ LLStyle::Params::Params() selected_color("selected_color", LLColor4::black), font("font", LLFontGL::getFontMonospace()), image("image"), - link_href("href") + link_href("href"), + is_link("is_link") {} @@ -51,6 +52,7 @@ LLStyle::LLStyle(const LLStyle::Params& p) mSelectedColor(p.selected_color), mFont(p.font()), mLink(p.link_href), + mIsLink(p.is_link.isProvided() ? p.is_link : !p.link_href().empty()), mDropShadow(p.drop_shadow), mImagep(p.image()) {} @@ -73,7 +75,7 @@ void LLStyle::setLinkHREF(const std::string& href) BOOL LLStyle::isLink() const { - return mLink.size(); + return mIsLink; } BOOL LLStyle::isVisible() const @@ -86,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 66cd639936..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; @@ -46,6 +47,7 @@ public: Optional<const LLFontGL*> font; Optional<LLUIImage*> image; Optional<std::string> link_href; + Optional<bool> is_link; Params(); }; LLStyle(const Params& p = Params()); @@ -71,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); @@ -106,7 +108,8 @@ private: std::string mFontName; const LLFontGL* mFont; std::string mLink; - LLUIImagePtr mImagep; + bool mIsLink; + LLPointer<LLUIImage> mImagep; }; typedef LLPointer<LLStyle> LLStyleSP; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 574b24cf13..349dbc3405 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2009-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 @@ -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; @@ -475,8 +477,8 @@ void LLTextBase::drawCursor() { LLColor4 text_color; const LLFontGL* fontp; - text_color = segmentp->getColor(); - fontp = segmentp->getStyle()->getFont(); + text_color = segmentp->getColor(); + fontp = segmentp->getStyle()->getFont(); fontp->render(text, mCursorPos, cursor_rect, LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha), LLFontGL::LEFT, mVAlign, @@ -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; @@ -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)) { @@ -1341,9 +1364,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 +1376,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 +1437,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 +1446,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()); @@ -1591,7 +1614,10 @@ void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& // appendText modifies mCursorPos... appendText(text, false, input_params); // ...so move cursor to top after appending text - startOfDoc(); + if (!mTrackEnd) + { + startOfDoc(); + } onValueChange(0, getLength()); } @@ -1602,28 +1628,42 @@ std::string LLTextBase::getText() const return getViewModel()->getValue().asString(); } +// IDEVO - icons can be UI image names or UUID sent from +// server with avatar display name +static LLUIImagePtr image_from_icon_name(const std::string& icon_name) +{ + if (LLUUID::validate(icon_name)) + { + return LLUI::getUIImageByID( LLUUID(icon_name) ); + } + else + { + return LLUI::getUIImage(icon_name); + } +} + void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) { LLStyle::Params style_params(input_params); style_params.fillFrom(getDefaultStyleParams()); S32 part = (S32)LLTextParser::WHOLE; - if(mParseHTML) + if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). { S32 start=0,end=0; LLUrlMatch match; std::string text = new_text; while ( LLUrlRegistry::instance().findUrl(text, match, - boost::bind(&LLTextBase::replaceUrlLabel, this, _1, _2)) ) + boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3)) ) { + + LLTextUtil::processUrlMatch(&match,this); + start = match.getStart(); end = match.getEnd()+1; - LLStyle::Params link_params = style_params; - link_params.color = match.getColor(); - link_params.readonly_color = match.getColor(); - link_params.font.style("UNDERLINE"); - link_params.link_href = match.getUrl(); + LLStyle::Params link_params(style_params); + link_params.overwriteFrom(match.getStyle()); // output the text before the Url if (start > 0) @@ -1640,30 +1680,20 @@ 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 (unless we've been asked to suppress hyperlinking) - if (match.isLinkDisabled()) + // output the styled Url + appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); + + // set the tooltip for the Url label + if (! match.getTooltip().empty()) { - appendAndHighlightText(match.getLabel(), part, style_params); + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(match.getTooltip()); + } } - else - { - appendAndHighlightText(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); - // set the tooltip for the Url label - if (! match.getTooltip().empty()) - { - segment_set_t::iterator it = getSegIterContaining(getLength()-1); - if (it != mSegments.end()) - { - LLTextSegmentPtr segment = *it; - segment->setToolTip(match.getTooltip()); - } - } - } // move on to the rest of the text after the Url if (end < (S32)text.length()) { @@ -1848,8 +1878,9 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlig } -void LLTextBase::replaceUrlLabel(const std::string &url, - const std::string &label) +void LLTextBase::replaceUrl(const std::string &url, + const std::string &label, + const std::string &icon) { // get the full (wide) text for the editor so we can change it LLWString text = getWText(); @@ -1870,7 +1901,7 @@ void LLTextBase::replaceUrlLabel(const std::string &url, seg->setEnd(seg_start + seg_length); // if we find a link with our Url, then replace the label - if (style->isLink() && style->getLinkHREF() == url) + if (style->getLinkHREF() == url) { S32 start = seg->getStart(); S32 end = seg->getEnd(); @@ -1879,6 +1910,21 @@ void LLTextBase::replaceUrlLabel(const std::string &url, modified = true; } + // Icon might be updated when more avatar or group info + // becomes available + if (style->isImage() && style->getLinkHREF() == url) + { + LLUIImagePtr image = image_from_icon_name( icon ); + if (image) + { + LLStyle::Params icon_params; + icon_params.image = image; + LLStyleConstSP new_style(new LLStyle(icon_params)); + seg->setStyle(new_style); + modified = true; + } + } + // work out the character offset for the next segment seg_start = seg->getEnd(); } @@ -1901,7 +1947,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 @@ -1912,9 +1958,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()) { @@ -1922,7 +1971,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; @@ -1944,7 +1993,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; @@ -1973,11 +2022,10 @@ 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); + pos = llclamp(line_iter->mDocIndexEnd - 1, 0, getLength()); break; } start_x += text_width; @@ -2193,19 +2241,39 @@ bool LLTextBase::scrolledToEnd() return mScroller->isAtBottom(); } - bool LLTextBase::setCursor(S32 row, S32 column) { - if (0 <= row && row < (S32)mLineInfoList.size()) + if (row < 0 || column < 0) return false; + + S32 n_lines = mLineInfoList.size(); + for (S32 line = row; line < n_lines; ++line) { - S32 doc_pos = mLineInfoList[row].mDocIndexStart; - column = llclamp(column, 0, mLineInfoList[row].mDocIndexEnd - mLineInfoList[row].mDocIndexStart - 1); - doc_pos += column; - updateCursorXPos(); + const line_info& li = mLineInfoList[line]; + + if (li.mLineNum < row) + { + continue; + } + else if (li.mLineNum > row) + { + break; // invalid column specified + } + + // Found the given row. + S32 line_length = li.mDocIndexEnd - li.mDocIndexStart;; + if (column >= line_length) + { + column -= line_length; + continue; + } + // Found the given column. + updateCursorXPos(); + S32 doc_pos = li.mDocIndexStart + column; return setCursorPos(doc_pos); } - return false; + + return false; // invalid row or column specified } @@ -2360,10 +2428,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 @@ -2481,21 +2576,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); @@ -2507,14 +2602,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 ) @@ -2524,14 +2619,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; } @@ -2869,11 +2964,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; } @@ -2883,18 +2985,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 e5dfecad54..7d545a1ba6 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -235,6 +235,7 @@ class LLTextBase public: friend class LLTextSegment; friend class LLNormalTextSegment; + friend class LLUICtrlFactory; struct LineSpacingParams : public LLInitParam::Choice<LineSpacingParams> { @@ -264,6 +265,7 @@ public: use_ellipses, parse_urls, parse_highlights, + clip, clip_partial; Optional<S32> v_pad, @@ -337,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; @@ -492,7 +494,11 @@ protected: // misc void updateRects(); void needsScroll() { mScrollNeeded = TRUE; } - void replaceUrlLabel(const std::string &url, const std::string &label); + + struct URLLabelCallback; + // Replace a URL with a new icon and label, for example, when + // avatar names are looked up. + void replaceUrl(const std::string &url, const std::string &label, const std::string& icon); void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false); @@ -547,6 +553,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..9bd445988d 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++; @@ -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) { @@ -1450,6 +1461,7 @@ void LLTextEditor::pasteHelper(bool is_primary) deselect(); onKeyStroke(); + mParseOnTheFly = TRUE; } @@ -2385,7 +2397,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 diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 58ecefdccb..9e4b95003b 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -315,6 +315,7 @@ private: BOOL mAllowEmbeddedItems; bool mShowContextMenu; + bool mParseOnTheFly; LLUUID mSourceID; diff --git a/indra/llui/lltoggleablemenu.cpp b/indra/llui/lltoggleablemenu.cpp index 0eb2dc1387..d29260750f 100644 --- a/indra/llui/lltoggleablemenu.cpp +++ b/indra/llui/lltoggleablemenu.cpp @@ -35,10 +35,22 @@ static LLDefaultChildRegistry::Register<LLToggleableMenu> r("toggleable_menu"); LLToggleableMenu::LLToggleableMenu(const LLToggleableMenu::Params& p) : LLMenuGL(p), mButtonRect(), + mVisibilityChangeSignal(NULL), mClosedByButtonClick(false) { } +LLToggleableMenu::~LLToggleableMenu() +{ + delete mVisibilityChangeSignal; +} + +boost::signals2::connection LLToggleableMenu::setVisibilityChangeCallback(const commit_signal_t::slot_type& cb) +{ + if (!mVisibilityChangeSignal) mVisibilityChangeSignal = new commit_signal_t(); + return mVisibilityChangeSignal->connect(cb); +} + // virtual void LLToggleableMenu::handleVisibilityChange (BOOL curVisibilityIn) { @@ -49,6 +61,12 @@ void LLToggleableMenu::handleVisibilityChange (BOOL curVisibilityIn) { mClosedByButtonClick = true; } + + if (mVisibilityChangeSignal) + { + (*mVisibilityChangeSignal)(this, + LLSD().with("visibility", curVisibilityIn).with("closed_by_button_click", mClosedByButtonClick)); + } } void LLToggleableMenu::setButtonRect(const LLRect& rect, LLView* current_view) diff --git a/indra/llui/lltoggleablemenu.h b/indra/llui/lltoggleablemenu.h index f036cdfffb..2094bd776f 100644 --- a/indra/llui/lltoggleablemenu.h +++ b/indra/llui/lltoggleablemenu.h @@ -41,6 +41,10 @@ protected: LLToggleableMenu(const Params&); friend class LLUICtrlFactory; public: + ~LLToggleableMenu(); + + boost::signals2::connection setVisibilityChangeCallback( const commit_signal_t::slot_type& cb ); + virtual void handleVisibilityChange (BOOL curVisibilityIn); const LLRect& getButtonRect() const { return mButtonRect; } @@ -57,6 +61,7 @@ public: protected: bool mClosedByButtonClick; LLRect mButtonRect; + commit_signal_t* mVisibilityChangeSignal; }; #endif // LL_LLTOGGLEABLEMENU_H diff --git a/indra/llui/lltooltip.cpp b/indra/llui/lltooltip.cpp index b02d3122fe..6390039794 100644 --- a/indra/llui/lltooltip.cpp +++ b/indra/llui/lltooltip.cpp @@ -78,9 +78,9 @@ BOOL LLToolTipView::handleHover(S32 x, S32 y, MASK mask) LLToolTipMgr& tooltip_mgr = LLToolTipMgr::instance(); - if (x != last_x && y != last_y) + if (x != last_x && y != last_y && !tooltip_mgr.getMouseNearRect().pointInRect(x, y)) { - // allow new tooltips because mouse moved + // allow new tooltips because mouse moved outside of mouse near rect tooltip_mgr.unblockToolTips(); } @@ -276,8 +276,8 @@ void LLToolTip::initFromParams(const LLToolTip::Params& p) if (p.styled_message.isProvided()) { - for (LLInitParam::ParamIterator<LLToolTip::StyledText>::const_iterator text_it = p.styled_message().begin(); - text_it != p.styled_message().end(); + for (LLInitParam::ParamIterator<LLToolTip::StyledText>::const_iterator text_it = p.styled_message.begin(); + text_it != p.styled_message.end(); ++text_it) { mTextBox->appendText(text_it->text(), false, text_it->style); @@ -580,6 +580,7 @@ void LLToolTipMgr::updateToolTipVisibility() if (mToolTip->getVisibleTime() > tooltip_timeout) { hideToolTips(); + unblockToolTips(); } } } 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 d33d8e3178..8020ca802b 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -61,6 +61,8 @@ // for XUIParse #include "llquaternion.h" #include <boost/tokenizer.hpp> +#include <boost/algorithm/string/find_iterator.hpp> +#include <boost/algorithm/string/finder.hpp> // // Globals @@ -950,7 +952,7 @@ 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; @@ -971,11 +973,11 @@ void gl_rect_2d_checkerboard(const LLRect& rect) gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); // ...white squares - gGL.color3f( 1.f, 1.f, 1.f ); + gGL.color4f( 1.f, 1.f, 1.f, alpha ); gl_rect_2d(rect); // ...gray squares - gGL.color3f( .7f, .7f, .7f ); + gGL.color4f( .7f, .7f, .7f, alpha ); gGL.flush(); glPolygonStipple( checkerboard ); @@ -1596,23 +1598,25 @@ void LLUI::initClass(const settings_map_t& settings, sWindow = NULL; // set later in startup LLFontGL::sShadowColor = LLUIColorTable::instance().getColor("ColorDropShadow"); + LLUICtrl::CommitCallbackRegistry::Registrar& reg = LLUICtrl::CommitCallbackRegistry::defaultRegistrar(); + // 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)); + reg.add("Floater.Toggle", boost::bind(&LLFloaterReg::toggleFloaterInstance, _2)); + reg.add("Floater.Show", boost::bind(&LLFloaterReg::showFloaterInstance, _2)); + reg.add("Floater.Hide", boost::bind(&LLFloaterReg::hideFloaterInstance, _2)); + reg.add("Floater.InitToVisibilityControl", boost::bind(&LLFloaterReg::initUICtrlToFloaterVisibilityControl, _1, _2)); // 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)); + 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)); @@ -1620,8 +1624,11 @@ void LLUI::initClass(const settings_map_t& settings, void LLUI::cleanupClass() { + if(sImageProvider) + { sImageProvider->cleanUp(); } +} void LLUI::setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t& remove_popup, const clear_popups_t& clear_popups) { @@ -1796,7 +1803,8 @@ void LLUI::setupPaths() LLXMLNodePtr root; BOOL success = LLXMLNode::parseFile(filename, root, NULL); Paths paths; - LLXUIParser::instance().readXUI(root, paths, filename); + LLXUIParser parser; + parser.readXUI(root, paths, filename); sXUIPaths.clear(); @@ -1805,14 +1813,14 @@ void LLUI::setupPaths() LLStringUtil::format_map_t path_args; path_args["[LANGUAGE]"] = LLUI::getLanguage(); - for (LLInitParam::ParamIterator<Directory>::const_iterator it = paths.directories().begin(), - end_it = paths.directories().end(); + for (LLInitParam::ParamIterator<Directory>::const_iterator it = paths.directories.begin(), + end_it = paths.directories.end(); it != end_it; ++it) { std::string path_val_ui; - for (LLInitParam::ParamIterator<SubDir>::const_iterator subdir_it = it->subdirs().begin(), - subdir_end_it = it->subdirs().end(); + for (LLInitParam::ParamIterator<SubDir>::const_iterator subdir_it = it->subdirs.begin(), + subdir_end_it = it->subdirs.end(); subdir_it != subdir_end_it;) { path_val_ui += subdir_it->value(); @@ -2014,37 +2022,84 @@ void LLUI::positionViewNearMouse(LLView* view, S32 spawn_x, S32 spawn_y) view->translateIntoRectWithExclusion( virtual_window_rect, mouse_rect, FALSE ); } +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; +} + // LLLocalClipRect and LLScreenClipRect moved to lllocalcliprect.h/cpp 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(); } - void TypedParam<LLUIColor>::setValueFromBlock() const + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateValueFromBlock() { if (control.isProvided()) { - 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() + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateBlockFromValue() { - LLColor4 color = mData.mValue.get(); + LLColor4 color = getValue(); red.set(color.mV[VRED], false); green.set(color.mV[VGREEN], false); blue.set(color.mV[VBLUE], false); @@ -2052,38 +2107,32 @@ namespace LLInitParam control.set("", false); } - void TypeValues<LLUIColor>::declareValues() - { - declare("white", LLColor4::white); - declare("black", LLColor4::black); - declare("red", LLColor4::red); - declare("green", LLColor4::green); - declare("blue", LLColor4::blue); - } - bool ParamCompare<const LLFontGL*, false>::equals(const LLFontGL* a, const LLFontGL* b) { return !(a->getFontDesc() < b->getFontDesc()) && !(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(); } - 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; } @@ -2093,22 +2142,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() { - 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()), false); + size.set(LLFontGL::sizeFromFont(getValue()), false); + style.set(LLFontGL::getStringFromStyle(getValue()->getFontDesc().getStyle()), false); } } - 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"), @@ -2116,10 +2169,10 @@ namespace LLInitParam width("width"), height("height") { - setBlockFromValue(); + updateBlockFromValue(); } - void TypedParam<LLRect>::setValueFromBlock() const + void ParamValue<LLRect, TypeValues<LLRect> >::updateValueFromBlock() { LLRect rect; @@ -2180,40 +2233,41 @@ namespace LLInitParam rect.mBottom = bottom; rect.mTop = top; } - mData.mValue = rect; + updateValue(rect); } - void TypedParam<LLRect>::setBlockFromValue() + void ParamValue<LLRect, TypeValues<LLRect> >::updateBlockFromValue() { // 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 - 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); + LLRect& value = getValue(); + left.set(value.mLeft, false); + right.set(value.mRight, false); + bottom.set(value.mBottom, false); + top.set(value.mTop, false); + width.set(value.getWidth(), false); + height.set(value.getHeight(), false); } - 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(); } - 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() { - x.set(mData.mValue.mX, false); - y.set(mData.mValue.mY, false); + x.set(getValue().mX, false); + y.set(getValue().mY, false); } diff --git a/indra/llui/llui.h b/indra/llui/llui.h index fc545c85d5..c583d58d5a 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -33,15 +33,12 @@ #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 "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" // LLUIFactory @@ -79,7 +76,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); @@ -185,6 +182,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 +262,6 @@ private: // Moved LLLocalClipRect to lllocalcliprect.h -// Moved all LLHandle-related code to llhandle.h - //RN: maybe this needs to moved elsewhere? class LLImageProviderInterface { @@ -371,10 +393,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 +405,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(); }; 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(); }; - // 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(); }; template<> @@ -467,17 +470,17 @@ 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(); }; } diff --git a/indra/llui/lluicolortable.cpp b/indra/llui/lluicolortable.cpp index 9891e38f7b..9455d09cc0 100644 --- a/indra/llui/lluicolortable.cpp +++ b/indra/llui/lluicolortable.cpp @@ -56,8 +56,8 @@ void LLUIColorTable::insertFromParams(const Params& p, string_color_map_t& table typedef std::map<std::string, std::string> string_string_map_t; string_string_map_t unresolved_refs; - for(LLInitParam::ParamIterator<ColorEntryParams>::const_iterator it = p.color_entries().begin(); - it != p.color_entries().end(); + for(LLInitParam::ParamIterator<ColorEntryParams>::const_iterator it = p.color_entries.begin(); + it != p.color_entries.end(); ++it) { ColorEntryParams color_entry = *it; @@ -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); @@ -237,7 +243,8 @@ void LLUIColorTable::saveUserSettings() const } LLXMLNodePtr output_node = new LLXMLNode("colors", false); - LLXUIParser::instance().writeXUI(output_node, params); + LLXUIParser parser; + parser.writeXUI(output_node, params); if(!output_node->isNull()) { @@ -303,7 +310,8 @@ bool LLUIColorTable::loadFromFilename(const std::string& filename, string_color_ } Params params; - LLXUIParser::instance().readXUI(root, params, filename); + LLXUIParser parser; + parser.readXUI(root, params, filename); if(params.validateBlock()) { diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 3ac3bf8c41..0a06b5e74f 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; @@ -110,7 +113,8 @@ 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); } @@ -823,7 +827,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 +927,37 @@ 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 commit_signal_t::slot_type& cb ) { if (!mCommitSignal) mCommitSignal = new commit_signal_t(); diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index 76dfdf754c..b37e9f6b1b 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -120,6 +120,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); @@ -202,6 +209,11 @@ 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 ); @@ -283,6 +295,10 @@ protected: boost::signals2::connection mMakeVisibleControlConnection; LLControlVariable* mMakeInvisibleControlVariable; boost::signals2::connection mMakeInvisibleControlConnection; + + static F32 sActiveControlTransparency; + static F32 sInactiveControlTransparency; + private: BOOL mTabStop; @@ -290,6 +306,8 @@ private: BOOL mTentative; LLRootHandle<LLUICtrl> mUICtrlHandle; + ETypeTransparency mTransparencyType; + class DefaultTabGroupFirstSorter; }; diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index 15a382660e..25e7a31e90 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -42,7 +42,7 @@ #include "llquaternion.h" // this library includes -#include "llfloater.h" +#include "llpanel.h" LLFastTimer::DeclareTimer FTM_WIDGET_CONSTRUCTION("Widget Construction"); LLFastTimer::DeclareTimer FTM_INIT_FROM_PARAMS("Widget InitFromParams"); @@ -93,10 +93,12 @@ void LLUICtrlFactory::loadWidgetTemplate(const std::string& widget_tag, LLInitPa std::string filename = std::string("widgets") + gDirUtilp->getDirDelimiter() + widget_tag + ".xml"; LLXMLNodePtr root_node; - if (LLUICtrlFactory::getLayeredXMLNode(filename, root_node)) + std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getXUIPaths().front(), filename); + if (!full_filename.empty()) { - LLUICtrlFactory::instance().pushFileName(filename); - LLXUIParser::instance().readXUI(root_node, block, filename); + LLUICtrlFactory::instance().pushFileName(full_filename); + LLSimpleXUIParser parser; + parser.readXUI(full_filename, block); LLUICtrlFactory::instance().popFileName(); } } @@ -150,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); } @@ -171,70 +193,6 @@ bool LLUICtrlFactory::getLocalizedXMLNode(const std::string &xui_filename, LLXML } } -static LLFastTimer::DeclareTimer FTM_BUILD_FLOATERS("Build Floaters"); - -//----------------------------------------------------------------------------- -// buildFloater() -//----------------------------------------------------------------------------- -bool LLUICtrlFactory::buildFloater(LLFloater* floaterp, const std::string& filename, LLXMLNodePtr output_node) -{ - LLFastTimer timer(FTM_BUILD_FLOATERS); - LLXMLNodePtr root; - - //if exporting, only load the language being exported, - //instead of layering localized version on top of english - if (output_node) - { - if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root)) - { - llwarns << "Couldn't parse floater from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; - return false; - } - } - else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) - { - llwarns << "Couldn't parse floater from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; - return false; - } - - // root must be called floater - if( !(root->hasName("floater") || root->hasName("multi_floater")) ) - { - llwarns << "Root node should be named floater in: " << filename << llendl; - return false; - } - - bool res = true; - - lldebugs << "Building floater " << filename << llendl; - pushFileName(filename); - { - if (!floaterp->getFactoryMap().empty()) - { - mFactoryStack.push_front(&floaterp->getFactoryMap()); - } - - // for local registry callbacks; define in constructor, referenced in XUI or postBuild - floaterp->getCommitCallbackRegistrar().pushScope(); - floaterp->getEnableCallbackRegistrar().pushScope(); - - res = floaterp->initFloaterXML(root, floaterp->getParent(), filename, output_node); - - floaterp->setXMLFilename(filename); - - floaterp->getCommitCallbackRegistrar().popScope(); - floaterp->getEnableCallbackRegistrar().popScope(); - - if (!floaterp->getFactoryMap().empty()) - { - mFactoryStack.pop_front(); - } - } - popFileName(); - - return res; -} - //----------------------------------------------------------------------------- // saveToXML() //----------------------------------------------------------------------------- @@ -243,69 +201,6 @@ S32 LLUICtrlFactory::saveToXML(LLView* viewp, const std::string& filename) return 0; } -static LLFastTimer::DeclareTimer FTM_BUILD_PANELS("Build Panels"); - -//----------------------------------------------------------------------------- -// buildPanel() -//----------------------------------------------------------------------------- -BOOL LLUICtrlFactory::buildPanel(LLPanel* panelp, const std::string& filename, LLXMLNodePtr output_node) -{ - LLFastTimer timer(FTM_BUILD_PANELS); - BOOL didPost = FALSE; - LLXMLNodePtr root; - - //if exporting, only load the language being exported, - //instead of layering localized version on top of english - if (output_node) - { - if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root)) - { - llwarns << "Couldn't parse panel from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; - return didPost; - } - } - else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) - { - llwarns << "Couldn't parse panel from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; - return didPost; - } - - // root must be called panel - if( !root->hasName("panel" ) ) - { - llwarns << "Root node should be named panel in : " << filename << llendl; - return didPost; - } - - lldebugs << "Building panel " << filename << llendl; - - pushFileName(filename); - { - if (!panelp->getFactoryMap().empty()) - { - mFactoryStack.push_front(&panelp->getFactoryMap()); - } - - // for local registry callbacks; define in constructor, referenced in XUI or postBuild - panelp->getCommitCallbackRegistrar().pushScope(); - panelp->getEnableCallbackRegistrar().pushScope(); - - didPost = panelp->initPanelXML(root, NULL, output_node); - - panelp->getCommitCallbackRegistrar().popScope(); - panelp->getEnableCallbackRegistrar().popScope(); - - panelp->setXMLFilename(filename); - - if (!panelp->getFactoryMap().empty()) - { - mFactoryStack.pop_front(); - } - } - popFileName(); - return didPost; -} - //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -337,32 +232,9 @@ LLView *LLUICtrlFactory::createFromXML(LLXMLNodePtr node, LLView* parent, const return view; } -//----------------------------------------------------------------------------- -// createFactoryPanel() -//----------------------------------------------------------------------------- -LLPanel* LLUICtrlFactory::createFactoryPanel(const std::string& name) -{ - std::deque<const LLCallbackMap::map_t*>::iterator itor; - for (itor = mFactoryStack.begin(); itor != mFactoryStack.end(); ++itor) - { - const LLCallbackMap::map_t* factory_map = *itor; - - // Look up this panel's name in the map. - LLCallbackMap::map_const_iter_t iter = factory_map->find( name ); - if (iter != factory_map->end()) - { - // Use the factory to create the panel, instead of using a default LLPanel. - LLPanel *ret = (LLPanel*) iter->second.mCallback( iter->second.mData ); - return ret; - } - } - LLPanel::Params panel_p; - return create<LLPanel>(panel_p); -} - std::string LLUICtrlFactory::getCurFileName() { - return mFileNames.empty() ? "" : gDirUtilp->getWorkingDir() + gDirUtilp->getDirDelimiter() + mFileNames.back(); + return mFileNames.empty() ? "" : mFileNames.back(); } @@ -376,36 +248,6 @@ void LLUICtrlFactory::popFileName() mFileNames.pop_back(); } - -//----------------------------------------------------------------------------- - -//static -BOOL LLUICtrlFactory::getAttributeColor(LLXMLNodePtr node, const std::string& name, LLColor4& color) -{ - std::string colorstring; - BOOL res = node->getAttributeString(name.c_str(), colorstring); - if (res) - { - if (LLUIColorTable::instance().colorExists(colorstring)) - { - color.setVec(LLUIColorTable::instance().getColor(colorstring)); - } - else - { - res = FALSE; - } - } - if (!res) - { - res = LLColor4::parseColor(colorstring, &color); - } - if (!res) - { - res = node->getAttributeColor(name.c_str(), color); - } - return res; -} - //static void LLUICtrlFactory::setCtrlParent(LLView* view, LLView* parent, S32 tab_group) { @@ -421,28 +263,22 @@ std::string LLUICtrlFactory::findSkinnedFilename(const std::string& filename) return gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), filename); } -void LLUICtrlFactory::pushFactoryFunctions(const LLCallbackMap::map_t* map) -{ - mFactoryStack.push_back(map); -} - -void LLUICtrlFactory::popFactoryFunctions() -{ - if (!mFactoryStack.empty()) - { - mFactoryStack.pop_back(); - } -} - //static void LLUICtrlFactory::copyName(LLXMLNodePtr src, LLXMLNodePtr dest) { dest->setName(src->getName()->mString); } +template<typename T> +const LLInitParam::BaseBlock& get_empty_param_block() +{ + static typename T::Params params; + return params; +} + // adds a widget and its param block to various registries //static -void LLUICtrlFactory::registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, dummy_widget_creator_func_t creator_func, const std::string& tag) +void LLUICtrlFactory::registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, const std::string& tag) { // associate parameter block type with template .xml file std::string* existing_tag = LLWidgetNameRegistry::instance().getValue(param_block_type); @@ -462,17 +298,9 @@ void LLUICtrlFactory::registerWidget(const std::type_info* widget_type, const st } } LLWidgetNameRegistry::instance().defaultRegistrar().add(param_block_type, tag); - // associate widget type with factory function - LLDefaultWidgetRegistry::instance().defaultRegistrar().add(widget_type, creator_func); //FIXME: comment this in when working on schema generation //LLWidgetTypeRegistry::instance().defaultRegistrar().add(tag, widget_type); - //LLDefaultParamBlockRegistry::instance().defaultRegistrar().add(widget_type, &getEmptyParamBlock<T>); -} - -//static -dummy_widget_creator_func_t* LLUICtrlFactory::getDefaultWidgetFunc(const std::type_info* widget_type) -{ - return LLDefaultWidgetRegistry::instance().getValue(widget_type); + //LLDefaultParamBlockRegistry::instance().defaultRegistrar().add(widget_type, &get_empty_param_block<T>); } //static diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index 58ec5d8387..499b97f52d 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -27,24 +27,13 @@ #ifndef LLUICTRLFACTORY_H #define LLUICTRLFACTORY_H -#include "llcallbackmap.h" +#include "llfasttimer.h" #include "llinitparam.h" #include "llregistry.h" -#include "v4color.h" -#include "llfasttimer.h" - #include "llxuiparser.h" -#include <boost/function.hpp> -#include <iosfwd> -#include <stack> -#include <set> - -class LLPanel; -class LLFloater; class LLView; - // sort functor for typeid maps struct LLCompareTypeID { @@ -85,12 +74,6 @@ class LLWidgetNameRegistry : public LLRegistrySingleton<const std::type_info*, std::string, LLWidgetNameRegistry , LLCompareTypeID> {}; -// lookup factory functions for default widget instances by widget type -typedef LLView* (*dummy_widget_creator_func_t)(const std::string&); -class LLDefaultWidgetRegistry -: public LLRegistrySingleton<const std::type_info*, dummy_widget_creator_func_t, LLDefaultWidgetRegistry, LLCompareTypeID> -{}; - // lookup function for generating empty param block by widget type // this is used for schema generation //typedef const LLInitParam::BaseBlock& (*empty_param_block_func_t)(); @@ -157,58 +140,21 @@ public: return ParamDefaults<typename T::Params, 0>::instance().get(); } - bool buildFloater(LLFloater* floaterp, const std::string &filename, LLXMLNodePtr output_node); - BOOL buildPanel(LLPanel* panelp, const std::string &filename, LLXMLNodePtr output_node = NULL); - // Does what you want for LLFloaters and LLPanels // Returns 0 on success S32 saveToXML(LLView* viewp, const std::string& filename); + // filename tracking for debugging info std::string getCurFileName(); void pushFileName(const std::string& name); void popFileName(); - static BOOL getAttributeColor(LLXMLNodePtr node, const std::string& name, LLColor4& color); - - LLPanel* createFactoryPanel(const std::string& name); - - void pushFactoryFunctions(const LLCallbackMap::map_t* map); - void popFactoryFunctions(); - - template<typename T> - static T* createWidget(const typename T::Params& params, LLView* parent = NULL) - { - T* widget = NULL; - - if (!params.validateBlock()) - { - llwarns << getInstance()->getCurFileName() << ": Invalid parameter block for " << typeid(T).name() << llendl; - //return NULL; - } - - { - LLFastTimer timer(FTM_WIDGET_CONSTRUCTION); - widget = new T(params); - } - { - LLFastTimer timer(FTM_INIT_FROM_PARAMS); - widget->initFromParams(params); - } - - if (parent) - { - S32 tab_group = params.tab_group.isProvided() ? params.tab_group() : S32_MAX; - setCtrlParent(widget, parent, tab_group); - } - return widget; - } - template<typename T> static T* create(typename T::Params& params, LLView* parent = NULL) { params.fillFrom(ParamDefaults<typename T::Params, 0>::instance().get()); - T* widget = createWidget<T>(params, parent); + T* widget = createWidgetImpl<T>(params, parent); if (widget) { widget->postBuild(); @@ -266,21 +212,54 @@ fail: template<class T> static T* getDefaultWidget(const std::string& name) { - dummy_widget_creator_func_t* dummy_func = getDefaultWidgetFunc(&typeid(T)); - return dummy_func ? dynamic_cast<T*>((*dummy_func)(name)) : NULL; + typename T::Params widget_params; + widget_params.name = name; + return create<T>(widget_params); } - template <class T> - static LLView* createDefaultWidget(const std::string& name) - { - typename T::Params params; - params.name(name); - - return create<T>(params); - } + static void createChildren(LLView* viewp, LLXMLNodePtr node, const widget_registry_t&, LLXMLNodePtr output_node = NULL); + + static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root); + static bool getLocalizedXMLNode(const std::string &xui_filename, LLXMLNodePtr& root); + +private: + //NOTE: both friend declarations are necessary to keep both gcc and msvc happy + template <typename T> friend class LLChildRegistry; + template <typename T> template <typename U> friend class LLChildRegistry<T>::Register; static void copyName(LLXMLNodePtr src, LLXMLNodePtr dest); + // helper function for adding widget type info to various registries + static void registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, const std::string& tag); + + static void loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block); + + template<typename T> + static T* createWidgetImpl(const typename T::Params& params, LLView* parent = NULL) + { + T* widget = NULL; + + if (!params.validateBlock()) + { + llwarns << getInstance()->getCurFileName() << ": Invalid parameter block for " << typeid(T).name() << llendl; + //return NULL; + } + + { LLFastTimer _(FTM_WIDGET_CONSTRUCTION); + widget = new T(params); + } + { LLFastTimer _(FTM_INIT_FROM_PARAMS); + widget->initFromParams(params); + } + + if (parent) + { + S32 tab_group = params.tab_group.isProvided() ? params.tab_group() : S32_MAX; + setCtrlParent(widget, parent, tab_group); + } + return widget; + } + template<typename T> static T* defaultBuilder(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node) { @@ -288,7 +267,8 @@ fail: typename T::Params params(getDefaultParams<T>()); - LLXUIParser::instance().readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + LLXUIParser parser; + parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); if (output_node) { @@ -298,14 +278,13 @@ fail: // Export only the differences between this any default params typename T::Params default_params(getDefaultParams<T>()); copyName(node, output_node); - LLXUIParser::instance().writeXUI( - output_node, output_params, &default_params); + parser.writeXUI(output_node, output_params, &default_params); } // Apply layout transformations, usually munging rect params.from_xui = true; T::applyXUILayout(params, parent); - T* widget = createWidget<T>(params, parent); + T* widget = createWidgetImpl<T>(params, parent); typedef typename T::child_registry_t registry_t; @@ -320,20 +299,6 @@ fail: return widget; } - static void createChildren(LLView* viewp, LLXMLNodePtr node, const widget_registry_t&, LLXMLNodePtr output_node = NULL); - - static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root); - - static bool getLocalizedXMLNode(const std::string &xui_filename, LLXMLNodePtr& root); - - static void loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block); - - // helper function for adding widget type info to various registries - static void registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, dummy_widget_creator_func_t creator_func, const std::string& tag); - -private: - // return default widget instance factory func for a given type - static dummy_widget_creator_func_t* getDefaultWidgetFunc(const std::type_info* widget_type); static const std::string* getWidgetTag(const std::type_info* widget_type); @@ -343,20 +308,10 @@ private: // Avoid directly using LLUI and LLDir in the template code static std::string findSkinnedFilename(const std::string& filename); - typedef std::deque<const LLCallbackMap::map_t*> factory_stack_t; - factory_stack_t mFactoryStack; - - LLPanel* mDummyPanel; + class LLPanel* mDummyPanel; std::vector<std::string> mFileNames; }; -template<typename T> -const LLInitParam::BaseBlock& getEmptyParamBlock() -{ - static typename T::Params params; - return params; -} - // this is here to make gcc happy with reference to LLUICtrlFactory template<typename DERIVED> template<typename T> @@ -364,7 +319,7 @@ LLChildRegistry<DERIVED>::Register<T>::Register(const char* tag, LLWidgetCreator : LLChildRegistry<DERIVED>::StaticRegistrar(tag, func.empty() ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder<T> : func) { // add this widget to various registries - LLUICtrlFactory::instance().registerWidget(&typeid(T), &typeid(typename T::Params), &LLUICtrlFactory::createDefaultWidget<T>, tag); + LLUICtrlFactory::instance().registerWidget(&typeid(T), &typeid(typename T::Params), tag); // since registry_t depends on T, do this in line here // TODO: uncomment this for schema generation @@ -372,58 +327,4 @@ LLChildRegistry<DERIVED>::Register<T>::Register(const char* tag, LLWidgetCreator //LLChildRegistryRegistry::instance().defaultRegistrar().add(&typeid(T), registry_t::instance()); } - -typedef boost::function<LLPanel* (void)> LLPanelClassCreatorFunc; - -// local static instance for registering a particular panel class - -class LLRegisterPanelClass -: public LLSingleton< LLRegisterPanelClass > -{ -public: - // reigister with either the provided builder, or the generic templated builder - void addPanelClass(const std::string& tag,LLPanelClassCreatorFunc func) - { - mPanelClassesNames[tag] = func; - } - - LLPanel* createPanelClass(const std::string& tag) - { - param_name_map_t::iterator iT = mPanelClassesNames.find(tag); - if(iT == mPanelClassesNames.end()) - return 0; - return iT->second(); - } - template<typename T> - static T* defaultPanelClassBuilder() - { - T* pT = new T(); - return pT; - } - -private: - typedef std::map< std::string, LLPanelClassCreatorFunc> param_name_map_t; - - param_name_map_t mPanelClassesNames; -}; - - -// local static instance for registering a particular panel class -template<typename T> -class LLRegisterPanelClassWrapper -: public LLRegisterPanelClass -{ -public: - // reigister with either the provided builder, or the generic templated builder - LLRegisterPanelClassWrapper(const std::string& tag); -}; - - -template<typename T> -LLRegisterPanelClassWrapper<T>::LLRegisterPanelClassWrapper(const std::string& tag) -{ - LLRegisterPanelClass::instance().addPanelClass(tag,&LLRegisterPanelClass::defaultPanelClassBuilder<T>); -} - - #endif //LLUICTRLFACTORY_H diff --git a/indra/llui/lluiimage.cpp b/indra/llui/lluiimage.cpp index 1ffad4806e..f37947a50b 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() { - if (mData.mValue == NULL) + if (getValue() == NULL) { name.set("none", false); } else { - name.set(mData.mValue->getName(), false); + name.set(getValue()->getName(), false); } } diff --git a/indra/llui/lluiimage.h b/indra/llui/lluiimage.h index 38107c112d..139d88e0ac 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(); + addSynonym(name, "name"); } - void setValueFromBlock() const; - void setBlockFromValue(); + void updateValueFromBlock(); + void updateBlockFromValue(); }; // Need custom comparison function for our test app, which only loads diff --git a/indra/llui/lluistring.cpp b/indra/llui/lluistring.cpp index 3e9b956ee6..ac69d3bf85 100644 --- a/indra/llui/lluistring.cpp +++ b/indra/llui/lluistring.cpp @@ -34,7 +34,7 @@ LLFastTimer::DeclareTimer FTM_UI_STRING("UI String"); LLUIString::LLUIString(const std::string& instring, const LLStringUtil::format_map_t& args) : mOrig(instring), - mArgs(args) + mArgs(new LLStringUtil::format_map_t(args)) { dirty(); } @@ -48,7 +48,7 @@ void LLUIString::assign(const std::string& s) void LLUIString::setArgList(const LLStringUtil::format_map_t& args) { - mArgs = args; + getArgs() = args; dirty(); } @@ -68,7 +68,7 @@ void LLUIString::setArgs(const LLSD& sd) void LLUIString::setArg(const std::string& key, const std::string& replacement) { - mArgs[key] = replacement; + getArgs()[key] = replacement; dirty(); } @@ -129,14 +129,14 @@ void LLUIString::updateResult() const mResult = mOrig; // get the defailt args + local args - if (mArgs.empty()) + 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()); + combined_args.insert(mArgs->begin(), mArgs->end()); LLStringUtil::format(mResult, combined_args); } } @@ -147,3 +147,12 @@ void LLUIString::updateWResult() const mWResult = utf8str_to_wstring(getUpdatedResult()); } + +LLStringUtil::format_map_t& LLUIString::getArgs() +{ + if (!mArgs) + { + mArgs = new LLStringUtil::format_map_t; + } + return *mArgs; +} diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h index fc7ac37d99..cb40c85582 100644 --- a/indra/llui/lluistring.h +++ b/indra/llui/lluistring.h @@ -58,9 +58,10 @@ class LLUIString public: // These methods all perform appropriate argument substitution // and modify mOrig where appropriate - LLUIString() : mNeedsResult(false), mNeedsWResult(false) {} + LLUIString() : mArgs(NULL), mNeedsResult(false), mNeedsWResult(false) {} LLUIString(const std::string& instring, const LLStringUtil::format_map_t& args); - LLUIString(const std::string& instring) { assign(instring); } + LLUIString(const std::string& instring) : mArgs(NULL) { assign(instring); } + ~LLUIString() { delete mArgs; } void assign(const std::string& instring); LLUIString& operator=(const std::string& s) { assign(s); return *this; } @@ -80,15 +81,15 @@ public: S32 length() const { return getUpdatedWResult().size(); } void clear(); - void clearArgs() { mArgs.clear(); } - + void clearArgs() { if (mArgs) mArgs->clear(); } + // These utility functions are included for text editing. // They do not affect mOrig and do not perform argument substitution void truncate(S32 maxchars); void erase(S32 charidx, S32 len); void insert(S32 charidx, const LLWString& wchars); void replace(S32 charidx, llwchar wc); - + private: // something changed, requiring reformatting of strings void dirty(); @@ -99,11 +100,12 @@ private: // do actual work of updating strings (non-inlined) void updateResult() const; void updateWResult() const; - + LLStringUtil::format_map_t& getArgs(); + std::string mOrig; mutable std::string mResult; mutable LLWString mWResult; // for displaying - LLStringUtil::format_map_t mArgs; + LLStringUtil::format_map_t* mArgs; // controls lazy evaluation mutable bool mNeedsResult; diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 5680ab8bd4..9db1feafd1 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -27,22 +27,25 @@ #include "linden_common.h" #include "llurlentry.h" +#include "lluictrl.h" #include "lluri.h" #include "llurlmatch.h" #include "llurlregistry.h" +#include "llavatarnamecache.h" #include "llcachename.h" #include "lltrans.h" #include "lluicolortable.h" +#include "message.h" #define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" +// Utility functions +std::string localize_slapp_label(const std::string& url, const std::string& full_name); -LLUrlEntryBase::LLUrlEntryBase() : - mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")), - mDisabledLink(false) -{ -} + +LLUrlEntryBase::LLUrlEntryBase() +{} LLUrlEntryBase::~LLUrlEntryBase() { @@ -53,6 +56,22 @@ std::string LLUrlEntryBase::getUrl(const std::string &string) const return escapeUrl(string); } +//virtual +std::string LLUrlEntryBase::getIcon(const std::string &url) +{ + return mIcon; +} + +LLStyle::Params LLUrlEntryBase::getStyle() const +{ + LLStyle::Params style_params; + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.font.style = "UNDERLINE"; + return style_params; +} + + std::string LLUrlEntryBase::getIDStringFromUrl(const std::string &url) const { // return the id from a SLURL in the format /app/{cmd}/{id}/about @@ -130,22 +149,35 @@ void LLUrlEntryBase::addObserver(const std::string &id, mObservers.insert(std::pair<std::string, LLUrlEntryObserver>(id, observer)); } } - -void LLUrlEntryBase::callObservers(const std::string &id, const std::string &label) + +// *NOTE: See also LLUrlEntryAgent::callObservers() +void LLUrlEntryBase::callObservers(const std::string &id, + const std::string &label, + const std::string &icon) { // notify all callbacks waiting on the given uuid - std::multimap<std::string, LLUrlEntryObserver>::iterator it; - for (it = mObservers.find(id); it != mObservers.end();) + typedef std::multimap<std::string, LLUrlEntryObserver>::iterator observer_it; + std::pair<observer_it, observer_it> matching_range = mObservers.equal_range(id); + for (observer_it it = matching_range.first; it != matching_range.second;) { // call the callback - give it the new label LLUrlEntryObserver &observer = it->second; - (*observer.signal)(it->second.url, label); + (*observer.signal)(it->second.url, label, icon); // then remove the signal - we only need to call it once delete observer.signal; mObservers.erase(it++); } } +/// 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); @@ -188,7 +220,13 @@ LLUrlEntryHTTPLabel::LLUrlEntryHTTPLabel() std::string LLUrlEntryHTTPLabel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) { - return getLabelFromWikiLink(url); + std::string label = getLabelFromWikiLink(url); + return (!LLUrlRegistry::instance().hasUrl(label)) ? label : getUrl(url); +} + +std::string LLUrlEntryHTTPLabel::getTooltip(const std::string &string) const +{ + return getUrl(string); } std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) const @@ -308,16 +346,35 @@ LLUrlEntryAgent::LLUrlEntryAgent() boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_agent.xml"; mIcon = "Generic_Person"; - mColor = LLUIColorTable::instance().getColor("AgentLinkColor"); } -void LLUrlEntryAgent::onAgentNameReceived(const LLUUID& id, - const std::string& first, - const std::string& last, - BOOL is_group) +// virtual +void LLUrlEntryAgent::callObservers(const std::string &id, + const std::string &label, + const std::string &icon) +{ + // notify all callbacks waiting on the given uuid + typedef std::multimap<std::string, LLUrlEntryObserver>::iterator observer_it; + std::pair<observer_it, observer_it> matching_range = mObservers.equal_range(id); + for (observer_it it = matching_range.first; it != matching_range.second;) + { + // call the callback - give it the new label + LLUrlEntryObserver &observer = it->second; + std::string final_label = localize_slapp_label(observer.url, label); + (*observer.signal)(observer.url, final_label, icon); + // then remove the signal - we only need to call it once + delete observer.signal; + mObservers.erase(it++); + } +} + +void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, + const LLAvatarName& av_name) { + std::string label = av_name.getCompleteName(); + // received the agent name from the server - tell our observers - callObservers(id.asString(), first + " " + last); + callObservers(id.asString(), label, mIcon); } LLUUID LLUrlEntryAgent::getID(const std::string &string) const @@ -330,6 +387,10 @@ std::string LLUrlEntryAgent::getTooltip(const std::string &string) const // return a tooltip corresponding to the URL type instead of the generic one std::string url = getUrl(string); + if (LLStringUtil::endsWith(url, "/inspect")) + { + return LLTrans::getString("TooltipAgentInspect"); + } if (LLStringUtil::endsWith(url, "/mute")) { return LLTrans::getString("TooltipAgentMute"); @@ -379,50 +440,182 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } LLUUID agent_id(agent_id_string); - std::string full_name; if (agent_id.isNull()) { return LLTrans::getString("AvatarNameNobody"); } - else if (gCacheName->getFullName(agent_id, full_name)) + + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_id, &av_name)) { - // customize label string based on agent SLapp suffix - if (LLStringUtil::endsWith(url, "/mute")) - { - return LLTrans::getString("SLappAgentMute") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/unmute")) - { - return LLTrans::getString("SLappAgentUnmute") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/im")) - { - return LLTrans::getString("SLappAgentIM") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/pay")) - { - return LLTrans::getString("SLappAgentPay") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/offerteleport")) - { - return LLTrans::getString("SLappAgentOfferTeleport") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/requestfriend")) - { - return LLTrans::getString("SLappAgentRequestFriend") + " " + full_name; - } - return full_name; + std::string label = av_name.getCompleteName(); + + // handle suffixes like /mute or /offerteleport + label = localize_slapp_label(url, label); + return label; + } + else + { + LLAvatarNameCache::get(agent_id, + boost::bind(&LLUrlEntryAgent::onAvatarNameCache, + this, _1, _2)); + addObserver(agent_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } +} + +LLStyle::Params LLUrlEntryAgent::getStyle() const +{ + LLStyle::Params style_params = LLUrlEntryBase::getStyle(); + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + return style_params; +} + +std::string localize_slapp_label(const std::string& url, const std::string& full_name) +{ + // customize label string based on agent SLapp suffix + if (LLStringUtil::endsWith(url, "/mute")) + { + return LLTrans::getString("SLappAgentMute") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/unmute")) + { + return LLTrans::getString("SLappAgentUnmute") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/im")) + { + return LLTrans::getString("SLappAgentIM") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/pay")) + { + return LLTrans::getString("SLappAgentPay") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/offerteleport")) + { + return LLTrans::getString("SLappAgentOfferTeleport") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/requestfriend")) + { + return LLTrans::getString("SLappAgentRequestFriend") + " " + full_name; + } + return full_name; +} + + +std::string LLUrlEntryAgent::getIcon(const std::string &url) +{ + // *NOTE: Could look up a badge here by calling getIDStringFromUrl() + // and looking up the badge for the agent. + return mIcon; +} + +// +// LLUrlEntryAgentName describes a Second Life agent name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +// +LLUrlEntryAgentName::LLUrlEntryAgentName() +{} + +void LLUrlEntryAgentName::onAvatarNameCache(const LLUUID& id, + const LLAvatarName& av_name) +{ + std::string label = getName(av_name); + // received the agent name from the server - tell our observers + callObservers(id.asString(), label, mIcon); +} + +std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + if (!gCacheName) + { + // probably at the login screen, use short string for layout + return LLTrans::getString("LoadingData"); + } + + std::string agent_id_string = getIDStringFromUrl(url); + if (agent_id_string.empty()) + { + // something went wrong, just give raw url + return unescapeUrl(url); + } + + LLUUID agent_id(agent_id_string); + if (agent_id.isNull()) + { + return LLTrans::getString("AvatarNameNobody"); + } + + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_id, &av_name)) + { + return getName(av_name); } else { - gCacheName->get(agent_id, FALSE, - boost::bind(&LLUrlEntryAgent::onAgentNameReceived, - this, _1, _2, _3, _4)); + LLAvatarNameCache::get(agent_id, + boost::bind(&LLUrlEntryAgentCompleteName::onAvatarNameCache, + this, _1, _2)); addObserver(agent_id_string, url, cb); return LLTrans::getString("LoadingData"); } } +LLStyle::Params LLUrlEntryAgentName::getStyle() const +{ + // don't override default colors + return LLStyle::Params().is_link(false); +} + +// +// LLUrlEntryAgentCompleteName describes a Second Life agent complete name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +// +LLUrlEntryAgentCompleteName::LLUrlEntryAgentCompleteName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/completename", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentCompleteName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.getCompleteName(); +} + +// +// LLUrlEntryAgentDisplayName describes a Second Life agent display name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +// +LLUrlEntryAgentDisplayName::LLUrlEntryAgentDisplayName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/displayname", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentDisplayName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.mDisplayName; +} + +// +// LLUrlEntryAgentUserName describes a Second Life agent user name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +// +LLUrlEntryAgentUserName::LLUrlEntryAgentUserName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/username", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentUserName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.mUsername.empty() ? avatar_name.getLegacyName() : avatar_name.mUsername; +} + // // LLUrlEntryGroup Describes a Second Life group Url, e.g., // secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about @@ -436,18 +629,16 @@ LLUrlEntryGroup::LLUrlEntryGroup() mMenuName = "menu_url_group.xml"; mIcon = "Generic_Group"; mTooltip = LLTrans::getString("TooltipGroupUrl"); - mColor = LLUIColorTable::instance().getColor("GroupLinkColor"); } void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id, - const std::string& first, - const std::string& last, - BOOL is_group) + const std::string& name, + bool is_group) { // received the group name from the server - tell our observers - callObservers(id.asString(), first); + callObservers(id.asString(), name, mIcon); } LLUUID LLUrlEntryGroup::getID(const std::string &string) const @@ -483,14 +674,23 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa } else { - gCacheName->get(group_id, TRUE, + gCacheName->getGroup(group_id, boost::bind(&LLUrlEntryGroup::onGroupNameReceived, - this, _1, _2, _3, _4)); + this, _1, _2, _3)); addObserver(group_id_string, url, cb); return LLTrans::getString("LoadingData"); } } +LLStyle::Params LLUrlEntryGroup::getStyle() const +{ + LLStyle::Params style_params = LLUrlEntryBase::getStyle(); + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + return style_params; +} + + // // LLUrlEntryInventory Describes a Second Life inventory Url, e.g., // secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select @@ -541,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 @@ -552,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 // @@ -607,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/ @@ -789,9 +1134,8 @@ 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); - mDisabledLink = true; } std::string LLUrlEntryNoLink::getUrl(const std::string &url) const @@ -805,6 +1149,13 @@ std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelC return getUrl(url); } +LLStyle::Params LLUrlEntryNoLink::getStyle() const +{ + // Don't render as URL (i.e. no context menu or hand cursor). + return LLStyle::Params().is_link(false); +} + + // // LLUrlEntryIcon describes an icon with <icon>...</icon> tags // @@ -812,7 +1163,6 @@ LLUrlEntryIcon::LLUrlEntryIcon() { mPattern = boost::regex("<icon\\s*>\\s*([^<]*)?\\s*</icon\\s*>", boost::regex::perl|boost::regex::icase); - mDisabledLink = true; } std::string LLUrlEntryIcon::getUrl(const std::string &url) const diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index e25eaa7555..5f82721c0f 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -30,13 +30,20 @@ #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> #include <map> +class LLAvatarName; + typedef boost::signals2::signal<void (const std::string& url, - const std::string& label)> LLUrlLabelSignal; + const std::string& label, + const std::string& icon)> LLUrlLabelSignal; typedef LLUrlLabelSignal::slot_type LLUrlLabelCallback; /// @@ -71,10 +78,10 @@ public: virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; } /// Return an icon that can be displayed next to Urls of this type - virtual std::string getIcon(const std::string &url) { return mIcon; } + virtual std::string getIcon(const std::string &url); - /// Return the color to render the displayed text - LLUIColor getColor() const { return mColor; } + /// Return the style to render the displayed text + virtual LLStyle::Params getStyle() const; /// Given a matched Url, return a tooltip string for the hyperlink virtual std::string getTooltip(const std::string &string) const { return mTooltip; } @@ -85,14 +92,13 @@ public: /// Return the name of a SL location described by this Url, if any virtual std::string getLocation(const std::string &url) const { return ""; } - /// is this a match for a URL that should not be hyperlinked? - bool isLinkDisabled() const { return mDisabledLink; } - /// Should this link text be underlined only when mouse is hovered over it? virtual bool underlineOnHoverOnly(const std::string &string) const { return false; } 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; @@ -100,7 +106,7 @@ protected: std::string getLabelFromWikiLink(const std::string &url) const; std::string getUrlFromWikiLink(const std::string &string) const; void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb); - void callObservers(const std::string &id, const std::string &label); + virtual void callObservers(const std::string &id, const std::string &label, const std::string& icon); typedef struct { std::string url; @@ -111,9 +117,7 @@ protected: std::string mIcon; std::string mMenuName; std::string mTooltip; - LLUIColor mColor; std::multimap<std::string, LLUrlEntryObserver> mObservers; - bool mDisabledLink; }; /// @@ -134,6 +138,7 @@ class LLUrlEntryHTTPLabel : public LLUrlEntryBase public: LLUrlEntryHTTPLabel(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getTooltip(const std::string &string) const; /*virtual*/ std::string getUrl(const std::string &string) const; }; @@ -162,18 +167,78 @@ public: /// /// LLUrlEntryAgent Describes a Second Life agent Url, e.g., /// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about -/// class LLUrlEntryAgent : public LLUrlEntryBase { public: LLUrlEntryAgent(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getIcon(const std::string &url); /*virtual*/ std::string getTooltip(const std::string &string) const; + /*virtual*/ LLStyle::Params getStyle() const; /*virtual*/ LLUUID getID(const std::string &string) const; /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; +protected: + /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); +private: + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); +}; + +/// +/// LLUrlEntryAgentName Describes a Second Life agent name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +/// that displays various forms of user name +/// This is a base class for the various implementations of name display +class LLUrlEntryAgentName : public LLUrlEntryBase, public boost::signals2::trackable +{ +public: + LLUrlEntryAgentName(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ LLStyle::Params getStyle() const; +protected: + // override this to pull out relevant name fields + virtual std::string getName(const LLAvatarName& avatar_name) = 0; private: - void onAgentNameReceived(const LLUUID& id, const std::string& first, - const std::string& last, BOOL is_group); + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); +}; + + +/// +/// LLUrlEntryAgentCompleteName Describes a Second Life agent name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +/// that displays the full display name + user name for an avatar +/// such as "James Linden (james.linden)" +class LLUrlEntryAgentCompleteName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentCompleteName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + +/// +/// LLUrlEntryAgentDisplayName Describes a Second Life agent display name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +/// that displays the just the display name for an avatar +/// such as "James Linden" +class LLUrlEntryAgentDisplayName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentDisplayName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + +/// +/// LLUrlEntryAgentUserName Describes a Second Life agent username Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +/// that displays the just the display name for an avatar +/// such as "james.linden" +class LLUrlEntryAgentUserName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentUserName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); }; /// @@ -185,10 +250,10 @@ class LLUrlEntryGroup : public LLUrlEntryBase public: LLUrlEntryGroup(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ LLStyle::Params getStyle() const; /*virtual*/ LLUUID getID(const std::string &string) const; private: - void onGroupNameReceived(const LLUUID& id, const std::string& first, - const std::string& last, BOOL is_group); + void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group); }; /// @@ -223,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; }; /// @@ -240,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/ /// @@ -297,6 +410,7 @@ public: LLUrlEntryNoLink(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ LLStyle::Params getStyle() const; }; /// diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index e53b0c4370..c1f1382a9f 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -37,16 +37,15 @@ LLUrlMatch::LLUrlMatch() : mIcon(""), mMenuName(""), mLocation(""), - mDisabledLink(false), mUnderlineOnHoverOnly(false) { } void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, - const std::string &icon, const LLUIColor& color, + const std::string &icon, const LLStyle::Params& style, const std::string &menu, const std::string &location, - bool disabled_link, const LLUUID& id, bool underline_on_hover_only) + const LLUUID& id, bool underline_on_hover_only) { mStart = start; mEnd = end; @@ -54,10 +53,10 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, mLabel = label; mTooltip = tooltip; mIcon = icon; - mColor = color; + mStyle = style; + mStyle.link_href = url; mMenuName = menu; mLocation = location; - mDisabledLink = disabled_link; mID = id; mUnderlineOnHoverOnly = underline_on_hover_only; } diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index d1b2112ee7..2818f45207 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -28,11 +28,11 @@ #ifndef LL_LLURLMATCH_H #define LL_LLURLMATCH_H -#include "linden_common.h" +//#include "linden_common.h" #include <string> #include <vector> -#include "lluicolor.h" +#include "llstyle.h" /// /// LLUrlMatch describes a single Url that was matched within a string by @@ -69,7 +69,7 @@ public: std::string getIcon() const { return mIcon; } /// Return the color to render the displayed text - LLUIColor getColor() const { return mColor; } + LLStyle::Params getStyle() const { return mStyle; } /// Return the name of a XUI file containing the context menu items std::string getMenuName() const { return mMenuName; } @@ -77,21 +77,17 @@ public: /// return the SL location that this Url describes, or "" if none. std::string getLocation() const { return mLocation; } - /// is this a match for a URL that should not be hyperlinked? - bool isLinkDisabled() const { return mDisabledLink; } - /// Should this link text be underlined only when mouse is hovered over it? bool underlineOnHoverOnly() const { return mUnderlineOnHoverOnly; } /// Change the contents of this match object (used by LLUrlRegistry) void setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, - const LLUIColor& color, const std::string &menu, - const std::string &location, bool disabled_link - , const LLUUID& id, bool underline_on_hover_only = false ); - - const LLUUID& getID() const { return mID;} + const LLStyle::Params& style, const std::string &menu, + const std::string &location, const LLUUID& id, + bool underline_on_hover_only = false ); + const LLUUID& getID() const { return mID; } private: U32 mStart; U32 mEnd; @@ -101,10 +97,8 @@ private: std::string mIcon; std::string mMenuName; std::string mLocation; - LLUUID mID; - LLUIColor mColor; - bool mDisabledLink; + LLStyle::Params mStyle; bool mUnderlineOnHoverOnly; }; diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 9d215cf7ef..523ee5d78c 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -31,22 +31,30 @@ #include <boost/regex.hpp> // default dummy callback that ignores any label updates from the server -void LLUrlRegistryNullCallback(const std::string &url, const std::string &label) +void LLUrlRegistryNullCallback(const std::string &url, const std::string &label, const std::string& icon) { } LLUrlRegistry::LLUrlRegistry() { + mUrlEntry.reserve(20); + // Urls are matched in the order that they were registered registerUrl(new LLUrlEntryNoLink()); registerUrl(new LLUrlEntryIcon()); registerUrl(new LLUrlEntrySLURL()); registerUrl(new LLUrlEntryHTTP()); registerUrl(new LLUrlEntryHTTPLabel()); + registerUrl(new LLUrlEntryAgentCompleteName()); + registerUrl(new LLUrlEntryAgentDisplayName()); + registerUrl(new LLUrlEntryAgentUserName()); + // LLUrlEntryAgent*Name must appear before LLUrlEntryAgent since + // LLUrlEntryAgent is a less specific (catchall for agent urls) registerUrl(new LLUrlEntryAgent()); registerUrl(new LLUrlEntryGroup()); registerUrl(new LLUrlEntryParcel()); registerUrl(new LLUrlEntryTeleport()); + registerUrl(new LLUrlEntryRegion()); registerUrl(new LLUrlEntryWorldMap()); registerUrl(new LLUrlEntryObjectIM()); registerUrl(new LLUrlEntryPlace()); @@ -71,10 +79,13 @@ LLUrlRegistry::~LLUrlRegistry() } } -void LLUrlRegistry::registerUrl(LLUrlEntryBase *url) +void LLUrlRegistry::registerUrl(LLUrlEntryBase *url, bool force_front) { if (url) { + if (force_front) // IDEVO + mUrlEntry.insert(mUrlEntry.begin(), url); + else mUrlEntry.push_back(url); } } @@ -174,10 +185,9 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL match_entry->getLabel(url, cb), match_entry->getTooltip(url), match_entry->getIcon(url), - match_entry->getColor(), + match_entry->getStyle(), match_entry->getMenuName(), match_entry->getLocation(url), - match_entry->isLinkDisabled(), match_entry->getID(url), match_entry->underlineOnHoverOnly(url)); return true; @@ -210,10 +220,9 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr match.getLabel(), match.getTooltip(), match.getIcon(), - match.getColor(), + match.getStyle(), match.getMenuName(), match.getLocation(), - match.isLinkDisabled(), match.getID(), match.underlineOnHoverOnly()); return true; diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index 24ce516c43..da16171a97 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -37,7 +37,9 @@ #include <vector> /// This default callback for findUrl() simply ignores any label updates -void LLUrlRegistryNullCallback(const std::string &url, const std::string &label); +void LLUrlRegistryNullCallback(const std::string &url, + const std::string &label, + const std::string &icon); /// /// LLUrlRegistry is a singleton that contains a set of Url types that @@ -64,7 +66,9 @@ public: ~LLUrlRegistry(); /// add a new Url handler to the registry (will be freed on destruction) - void registerUrl(LLUrlEntryBase *url); + /// optionally force it to the front of the list, making it take + /// priority over other regular expression matches for URLs + void registerUrl(LLUrlEntryBase *url, bool force_front = false); /// get the next Url in an input string, starting at a given character offset /// your callback is invoked if the matched Url's label changes in the future diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index fe5ef269a9..245126d178 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -102,11 +102,8 @@ LLView::Params::Params() left_pad("left_pad"), left_delta("left_delta", S32_MAX), from_xui("from_xui", false), - user_resize("user_resize"), - auto_resize("auto_resize"), + focus_root("focus_root", false), needs_translate("translate"), - min_width("min_width"), - max_width("max_width"), xmlns("xmlns"), xmlns_xsi("xmlns:xsi"), xsi_schemaLocation("xsi:schemaLocation"), @@ -117,16 +114,16 @@ LLView::Params::Params() } LLView::LLView(const LLView::Params& p) -: mName(p.name), +: mVisible(p.visible), + 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), @@ -167,8 +164,6 @@ LLView::~LLView() if (mDefaultWidgets) { - std::for_each(mDefaultWidgets->begin(), mDefaultWidgets->end(), - DeletePairedPointer()); delete mDefaultWidgets; mDefaultWidgets = NULL; } @@ -218,7 +213,7 @@ void LLView::setUseBoundingRect( BOOL use_bounding_rect ) } } -BOOL LLView::getUseBoundingRect() +BOOL LLView::getUseBoundingRect() const { return mUseBoundingRect; } @@ -343,7 +338,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(); } @@ -1304,7 +1299,13 @@ void LLView::drawChildren() { if (!mChildList.empty()) { - LLRect rootRect = getRootView()->getRect(); + static const LLRect* rootRect = NULL; + + if (!mParentView) + { + rootRect = &mRect; + } + LLRect screenRect; ++sDepth; @@ -1318,7 +1319,7 @@ void LLView::drawChildren() { // Only draw views that are within the root view localRectToScreen(viewp->getRect(),&screenRect); - if ( rootRect.overlaps(screenRect) && LLUI::sDirtyRect.overlaps(screenRect)) + if ( rootRect->overlaps(screenRect) && LLUI::sDirtyRect.overlaps(screenRect)) { LLUI::pushMatrix(); { @@ -1370,12 +1371,12 @@ void LLView::drawDebugRect() // drawing solids requires texturing be disabled gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - if (mUseBoundingRect) + if (getUseBoundingRect()) { LLUI::translate((F32)mBoundingRect.mLeft - (F32)mRect.mLeft, (F32)mBoundingRect.mBottom - (F32)mRect.mBottom, 0.f); } - LLRect debug_rect = mUseBoundingRect ? mBoundingRect : mRect; + LLRect debug_rect = getUseBoundingRect() ? mBoundingRect : mRect; // draw red rectangle for the border LLColor4 border_color(0.25f, 0.25f, 0.25f, 1.f); @@ -1573,7 +1574,7 @@ void LLView::updateBoundingRect() LLRect cur_rect = mBoundingRect; - if (mUseBoundingRect) + if (getUseBoundingRect()) { mBoundingRect = calcBoundingRect(); } @@ -1583,7 +1584,7 @@ void LLView::updateBoundingRect() } // give parent view a chance to resize, in case we just moved, for example - if (getParent() && getParent()->mUseBoundingRect) + if (getParent() && getParent()->getUseBoundingRect()) { getParent()->updateBoundingRect(); } @@ -1607,7 +1608,7 @@ LLRect LLView::calcScreenBoundingRect() const { LLRect screen_rect; // get bounding rect, if used - LLRect bounding_rect = mUseBoundingRect ? mBoundingRect : mRect; + LLRect bounding_rect = getUseBoundingRect() ? mBoundingRect : mRect; // convert to local coordinates, as defined by mRect bounding_rect.translate(-mRect.mLeft, -mRect.mBottom); @@ -1686,16 +1687,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) - { - child = LLUICtrlFactory::createDefaultWidget<LLView>(name); - } - } - return child; + return getChild<LLView>(name, recurse); } static LLFastTimer::DeclareTimer FTM_FIND_VIEWS("Find Widgets"); @@ -1736,14 +1728,14 @@ LLView* LLView::findChildView(const std::string& name, BOOL recurse) const BOOL LLView::parentPointInView(S32 x, S32 y, EHitTestType type) const { - return (mUseBoundingRect && type == HIT_TEST_USE_BOUNDING_RECT) + return (getUseBoundingRect() && type == HIT_TEST_USE_BOUNDING_RECT) ? mBoundingRect.pointInRect( x, y ) : mRect.pointInRect( x, y ); } BOOL LLView::pointInView(S32 x, S32 y, EHitTestType type) const { - return (mUseBoundingRect && type == HIT_TEST_USE_BOUNDING_RECT) + return (getUseBoundingRect() && type == HIT_TEST_USE_BOUNDING_RECT) ? mBoundingRect.pointInRect( x + mRect.mLeft, y + mRect.mBottom ) : mRect.localPointInRect( x, y ); } @@ -1972,7 +1964,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; @@ -2015,7 +2007,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; @@ -2806,11 +2798,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; } diff --git a/indra/llui/llview.h b/indra/llui/llview.h index f7175112bf..594a5eec6b 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" @@ -116,7 +117,8 @@ public: visible, mouse_opaque, use_bounding_rect, - from_xui; + from_xui, + focus_root; Optional<S32> tab_group, default_tab_group; @@ -128,26 +130,22 @@ public: Optional<std::string> layout; Optional<LLRect> rect; + // Historical bottom-left layout used bottom_delta and left_delta // for relative positioning. New layout "topleft" prefers specifying // based on top edge. - Optional<S32> bottom_delta, // deprecated - top_pad, // from last bottom to my top - top_delta, // from last top to my top - left_pad, // from last right to my left - left_delta; // from last left to my left - - // these are nested attributes for LLLayoutPanel + Optional<S32> bottom_delta, // from last bottom to my bottom + top_pad, // from last bottom to my top + top_delta, // from last top to my top + left_pad, // from last right to my left + left_delta; // from last left to my left + //FIXME: get parent context involved in parsing traversal - Ignored user_resize, - auto_resize, - needs_translate, - min_width, - max_width, - xmlns, - xmlns_xsi, - xsi_schemaLocation, - xsi_type; + Ignored needs_translate, // cue for translation tools + xmlns, // xml namespace + xmlns_xsi, // xml namespace + xsi_schemaLocation, // xml schema + xsi_type; // xml schema type Params(); }; @@ -238,7 +236,7 @@ public: void setSoundFlags(U8 flags) { mSoundFlags = flags; } void setName(std::string name) { mName = name; } void setUseBoundingRect( BOOL use_bounding_rect ); - BOOL getUseBoundingRect(); + BOOL getUseBoundingRect() const; ECursorType getHoverCursor() { return mHoverCursor; } @@ -277,6 +275,11 @@ public: BOOL focusNextRoot(); BOOL focusPrevRoot(); + // Normally we want the app menus to get priority on accelerated keys + // However, sometimes we want to give specific views a first chance + // iat handling them. (eg. the script editor) + virtual bool hasAccelerators() const { return false; }; + // delete all children. Override this function if you need to // perform any extra clean up such as cached pointers to selected // children, etc. @@ -285,7 +288,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 @@ -404,21 +407,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 @@ -465,12 +463,8 @@ 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()) - { - return NULL; - } - return dynamic_cast<T*>(found_it->second); + LLView* widgetp = getDefaultWidgetContainer().findChildView(name); + return dynamic_cast<T*>(widgetp); } ////////////////////////////////////////////// @@ -550,11 +544,13 @@ private: 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; @@ -576,17 +572,15 @@ private: LLRootHandle<LLView> mHandle; BOOL mLastVisible; - BOOL mVisible; - S32 mNextInsertionOrdinal; 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; public: // Depth in view hierarchy during rendering @@ -653,7 +647,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..32d7ea7c25 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"); diff --git a/indra/llui/llwindowshade.cpp b/indra/llui/llwindowshade.cpp new file mode 100644 index 0000000000..77e94385d4 --- /dev/null +++ b/indra/llui/llwindowshade.cpp @@ -0,0 +1,328 @@ +/** + * @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; + +LLWindowShade::Params::Params() +: bg_image("bg_image"), + modal("modal", false), + text_color("text_color"), + can_close("can_close", true) +{ + mouse_opaque = false; +} + +LLWindowShade::LLWindowShade(const LLWindowShade::Params& params) +: LLUICtrl(params), + mNotification(params.notification), + 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, 30, 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 = LLColor4(0.f, 0.f, 0.f, 0.2f); + 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, 23, 21, 8); + panel->addChild(LLUICtrlFactory::create<LLIconCtrl>(icon_p)); + + LLTextBox::Params text_p; + text_p.rect = LLRect(31, 20, panel->getRect().getWidth() - 5, 0); + 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, 30, 130, 0); + LLLayoutPanel* form_elements_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p); + stackp->addChild(form_elements_panel); + + if (params.can_close) + { + panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>(); + panel_p.auto_resize = false; + panel_p.user_resize = false; + panel_p.rect = LLRect(0, 30, 25, 0); + 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)); + } + + LLSD payload = mNotification->getPayload(); + + LLNotificationFormPtr formp = mNotification->getForm(); + LLLayoutPanel& notification_area = getChildRef<LLLayoutPanel>("notification_area"); + notification_area.getChild<LLUICtrl>("notification_icon")->setValue(mNotification->getIcon()); + notification_area.getChild<LLUICtrl>("notification_text")->setValue(mNotification->getMessage()); + notification_area.getChild<LLUICtrl>("notification_text")->setToolTip(mNotification->getMessage()); + + LLNotificationForm::EIgnoreType ignore_type = formp->getIgnoreType(); + LLLayoutPanel& form_elements = notification_area.getChildRef<LLLayoutPanel>("form_elements"); + form_elements.deleteAllChildren(); + + 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(), form_elements.getRect().getHeight() - 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 - FORM_PADDING_VERTICAL) + WIDGET_HEIGHT; + 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); + } +} + +void LLWindowShade::show() +{ + getChildRef<LLLayoutPanel>("notification_area").setVisible(true); + getChildRef<LLLayoutPanel>("background_area").setBackgroundVisible(mModal); + + setMouseOpaque(mModal); +} + +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() + 10, + llmin(mFormHeight, MAX_NOTIFICATION_AREA_HEIGHT), + MAX_NOTIFICATION_AREA_HEIGHT)); + + LLUICtrl::draw(); + if (mNotification && !mNotification->isActive()) + { + hide(); + } +} + +void LLWindowShade::hide() +{ + getChildRef<LLLayoutPanel>("notification_area").setVisible(false); + getChildRef<LLLayoutPanel>("background_area").setBackgroundVisible(false); + + setMouseOpaque(false); +} + +void LLWindowShade::onCloseNotification() +{ + LLNotifications::instance().cancel(mNotification); +} + +void LLWindowShade::onClickIgnore(LLUICtrl* ctrl) +{ + bool check = ctrl->getValue().asBoolean(); + if (mNotification && mNotification->getForm()->getIgnoreType() == LLNotificationForm::IGNORE_SHOW_AGAIN) + { + // question was "show again" so invert value to get "ignore" + check = !check; + } + mNotification->setIgnored(check); +} + +void LLWindowShade::onClickNotificationButton(const std::string& name) +{ + if (!mNotification) return; + + mNotificationResponse[name] = true; + + mNotification->respond(mNotificationResponse); +} + +void LLWindowShade::onEnterNotificationText(LLUICtrl* ctrl, const std::string& name) +{ + mNotificationResponse[name] = ctrl->getValue().asString(); +} diff --git a/indra/llui/llwindowshade.h b/indra/llui/llwindowshade.h new file mode 100644 index 0000000000..09ffc2cd54 --- /dev/null +++ b/indra/llui/llwindowshade.h @@ -0,0 +1,70 @@ +/** + * @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> + { + Mandatory<LLNotificationPtr> notification; + Optional<LLUIImage*> bg_image; + Optional<LLUIColor> text_color; + Optional<bool> modal, + can_close; + + Params(); + }; + + void show(); + /*virtual*/ void draw(); + void hide(); + +private: + 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); + + LLNotificationPtr mNotification; + 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 ff53ae5624..26b3b17577 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -27,11 +27,29 @@ #include "llstring.h" #include "llfile.h" +#include "llavatarnamecache.h" #include "llcachename.h" #include "lluuid.h" +#include "message.h" #include <string> +// Stub for LLAvatarNameCache +bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name) +{ + return false; +} + +void LLAvatarNameCache::get(const LLUUID& agent_id, callback_slot_t slot) +{ + return; +} + +bool LLAvatarNameCache::useDisplayNames() +{ + return false; +} + // // Stub implementation for LLCacheName // @@ -47,7 +65,12 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) return TRUE; } -boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback) +boost::signals2::connection LLCacheName::get(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback) +{ + return boost::signals2::connection(); +} + +boost::signals2::connection LLCacheName::getGroup(const LLUUID& id, const LLCacheNameCallback& callback) { return boost::signals2::connection(); } @@ -67,3 +90,120 @@ std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil:: { return std::string(); } + +// +// Stub implementation for LLStyle::Params::Params +// + +LLStyle::Params::Params() +{ +} + +// +// Stub implementations for various LLInitParam classes +// + +namespace LLInitParam +{ + BaseBlock::BaseBlock() {} + BaseBlock::~BaseBlock() {} + Param::Param(BaseBlock* enclosing_block) + : mIsProvided(false) + { + 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); + } + void BaseBlock::paramChanged(const Param& last_param, bool user_provided) {} + + 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, S32 generation){ 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; } + + ParamValue<LLUIColor, TypeValues<LLUIColor> >::ParamValue(const LLUIColor& color) + : super_t(color) + {} + + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateValueFromBlock() + {} + + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateBlockFromValue() + {} + + bool ParamCompare<const LLFontGL*, false>::equals(const LLFontGL* a, const LLFontGL* b) + { + return false; + } + + ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::ParamValue(const LLFontGL* fontp) + : super_t(fontp) + {} + + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateValueFromBlock() + {} + + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateBlockFromValue() + {} + + void TypeValues<LLFontGL::HAlign>::declareValues() + {} + + void TypeValues<LLFontGL::VAlign>::declareValues() + {} + + void TypeValues<LLFontGL::ShadowType>::declareValues() + {} + + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateValueFromBlock() + {} + + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateBlockFromValue() + {} + + + bool ParamCompare<LLUIImage*, false>::equals( + LLUIImage* const &a, + LLUIImage* const &b) + { + return false; + } + + bool ParamCompare<LLUIColor, false>::equals(const LLUIColor &a, const LLUIColor &b) + { + return false; + } + +} + +//static +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 95affe4460..2f814f4200 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -27,12 +27,22 @@ #include "linden_common.h" #include "../llurlentry.h" +#include "../lluictrl.h" #include "llurlentry_stub.cpp" #include "lltut.h" #include "../lluicolortable.h" +#include "../lluiimage.h" #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(); @@ -40,6 +50,42 @@ LLUIColor LLUIColorTable::getColor(const std::string& name, const LLColor4& defa LLUIColor::LLUIColor() : mColorPtr(NULL) {} +LLUIImage::LLUIImage(const std::string& name, LLPointer<LLTexture> image) +{ +} + +LLUIImage::~LLUIImage() +{ +} + +//virtual +S32 LLUIImage::getWidth() const +{ + return 0; +} + +//virtual +S32 LLUIImage::getHeight() const +{ + return 0; +} + +namespace LLInitParam +{ + S32 Parser::sNextParseGeneration = 0; + 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 @@ -73,6 +119,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>() { @@ -667,4 +752,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 4e38bea1bd..e09ef33d49 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -25,14 +25,147 @@ * $/LicenseInfo$ */ +#include "linden_common.h" + #include "../llurlmatch.h" +#include "../lluiimage.h" #include "lltut.h" -// link seam +// link seams + LLUIColor::LLUIColor() : mColorPtr(NULL) {} +LLStyle::Params::Params() +{ +} + +LLUIImage::LLUIImage(const std::string& name, LLPointer<LLTexture> image) +{ +} + +LLUIImage::~LLUIImage() +{ +} + +//virtual +S32 LLUIImage::getWidth() const +{ + return 0; +} + +//virtual +S32 LLUIImage::getHeight() const +{ + return 0; +} + +namespace LLInitParam +{ + BaseBlock::BaseBlock() {} + BaseBlock::~BaseBlock() {} + + S32 Parser::sNextParseGeneration = 0; + + 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::paramChanged(const Param& last_param, bool user_provided) {} + + 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; + } + + Param::Param(BaseBlock* enclosing_block) + : mIsProvided(false) + { + 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); + } + + bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, S32 generation){ 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; } + + ParamValue<LLUIColor, TypeValues<LLUIColor> >::ParamValue(const LLUIColor& color) + : super_t(color) + {} + + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateValueFromBlock() + {} + + void ParamValue<LLUIColor, TypeValues<LLUIColor> >::updateBlockFromValue() + {} + + bool ParamCompare<const LLFontGL*, false>::equals(const LLFontGL* a, const LLFontGL* b) + { + return false; + } + + + ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::ParamValue(const LLFontGL* fontp) + : super_t(fontp) + {} + + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateValueFromBlock() + {} + + void ParamValue<const LLFontGL*, TypeValues<const LLFontGL*> >::updateBlockFromValue() + {} + + void TypeValues<LLFontGL::HAlign>::declareValues() + {} + + void TypeValues<LLFontGL::VAlign>::declareValues() + {} + + void TypeValues<LLFontGL::ShadowType>::declareValues() + {} + + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateValueFromBlock() + {} + + void ParamValue<LLUIImage*, TypeValues<LLUIImage*> >::updateBlockFromValue() + {} + + bool ParamCompare<LLUIImage*, false>::equals( + LLUIImage* const &a, + LLUIImage* const &b) + { + return false; + } + + bool ParamCompare<LLUIColor, false>::equals(const LLUIColor &a, const LLUIColor &b) + { + return false; + } + +} + +//static +LLFontGL* LLFontGL::getFontDefault() +{ + return NULL; +} + + namespace tut { struct LLUrlMatchData @@ -59,7 +192,7 @@ namespace tut LLUrlMatch match; ensure("empty()", match.empty()); - match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure("! empty()", ! match.empty()); } @@ -72,7 +205,7 @@ namespace tut LLUrlMatch match; ensure_equals("getStart() == 0", match.getStart(), 0); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getStart() == 10", match.getStart(), 10); } @@ -85,7 +218,7 @@ namespace tut LLUrlMatch match; ensure_equals("getEnd() == 0", match.getEnd(), 0); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getEnd() == 20", match.getEnd(), 20); } @@ -98,10 +231,10 @@ namespace tut LLUrlMatch match; ensure_equals("getUrl() == ''", match.getUrl(), ""); - match.setValues(10, 20, "http://slurl.com/", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getUrl() == '' (2)", match.getUrl(), ""); } @@ -114,10 +247,10 @@ namespace tut LLUrlMatch match; ensure_equals("getLabel() == ''", match.getLabel(), ""); - match.setValues(10, 20, "", "Label", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getLabel() == '' (2)", match.getLabel(), ""); } @@ -130,10 +263,10 @@ namespace tut LLUrlMatch match; ensure_equals("getTooltip() == ''", match.getTooltip(), ""); - match.setValues(10, 20, "", "", "Info", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getTooltip() == '' (2)", match.getTooltip(), ""); } @@ -146,10 +279,10 @@ namespace tut LLUrlMatch match; ensure_equals("getIcon() == ''", match.getIcon(), ""); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getIcon() == '' (2)", match.getIcon(), ""); } @@ -162,10 +295,10 @@ namespace tut LLUrlMatch match; ensure("getMenuName() empty", match.getMenuName().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "", LLUUID::null); ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure("getMenuName() empty (2)", match.getMenuName().empty()); } @@ -178,10 +311,10 @@ namespace tut LLUrlMatch match; ensure("getLocation() empty", match.getLocation().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "Paris", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris", LLUUID::null); ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false,LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure("getLocation() empty (2)", match.getLocation().empty()); } } |