diff options
Diffstat (limited to 'indra/llui')
39 files changed, 1334 insertions, 168 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 34a08603fa..589ceac501 100755 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -75,6 +75,7 @@ set(llui_SOURCE_FILES llmultislider.cpp llmultisliderctrl.cpp llnotifications.cpp + llnotificationslistener.cpp llnotificationsutil.cpp llpanel.cpp llprogressbar.cpp @@ -128,6 +129,7 @@ set(llui_SOURCE_FILES llviewmodel.cpp llview.cpp llviewquery.cpp + llviewereventrecorder.cpp llwindowshade.cpp llxuiparser.cpp ) @@ -183,6 +185,7 @@ set(llui_HEADER_FILES llmultislider.h llnotificationptr.h llnotifications.h + llnotificationslistener.h llnotificationsutil.h llnotificationtemplate.h llnotificationvisibilityrule.h @@ -240,6 +243,7 @@ set(llui_HEADER_FILES llviewinject.h llviewmodel.h llview.h + llviewereventrecorder.h llviewquery.h llwindowshade.h llxuiparser.h diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 44f2c1efe9..3cfe5ac57f 100755 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -49,6 +49,7 @@ #include "lluictrlfactory.h" #include "llhelp.h" #include "lldockablefloater.h" +#include "llviewereventrecorder.h" static LLDefaultChildRegistry::Register<LLButton> r("button"); @@ -443,6 +444,8 @@ BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask) */ LLUICtrl::handleMouseDown(x, y, mask); + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + if(mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); mMouseDownTimer.start(); @@ -473,6 +476,7 @@ BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) * by calling LLUICtrl::mMouseUpSignal(x, y, mask); */ LLUICtrl::handleMouseUp(x, y, mask); + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); // Regardless of where mouseup occurs, handle callback if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); @@ -637,7 +641,7 @@ void LLButton::draw() bool use_glow_effect = FALSE; LLColor4 highlighting_color = LLColor4::white; - LLColor4 glow_color; + LLColor4 glow_color = LLColor4::white; LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA; LLUIImage* imagep = NULL; diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index e67f39b21b..3f13691a30 100755 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -62,8 +62,8 @@ public: virtual void draw(); virtual void onCommit(); - /*virtual*/ void onFocusReceived(); - /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + /*virtual*/ void onFocusLost(); void enableSingleLineMode(bool single_line_mode); boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb); diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index d4e14d9419..56be52f69a 100755 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -534,6 +534,13 @@ void LLComboBox::createLineEditor(const LLComboBox::Params& p) } } +void LLComboBox::setLeftTextPadding(S32 pad) +{ + S32 left_pad, right_pad; + mTextEntry->getTextPadding(&left_pad, &right_pad); + mTextEntry->setTextPadding(pad, right_pad); +} + void* LLComboBox::getCurrentUserdata() { LLScrollListItem* item = mList->getFirstSelected(); diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index 64dbaea306..67393504e7 100755 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -191,6 +191,8 @@ public: //======================================================================== + void setLeftTextPadding(S32 pad); + void* getCurrentUserdata(); void setPrearrangeCallback( commit_callback_t cb ) { mPrearrangeCallback = cb; } diff --git a/indra/llui/lldraghandle.cpp b/indra/llui/lldraghandle.cpp index 5f69c6af31..304d21d0df 100755 --- a/indra/llui/lldraghandle.cpp +++ b/indra/llui/lldraghandle.cpp @@ -315,14 +315,15 @@ BOOL LLDragHandle::handleHover(S32 x, S32 y, MASK mask) S32 delta_y = screen_y - mDragLastScreenY; // if dragging a docked floater we want to undock - if (((LLFloater*)getParent())->isDocked()) + LLFloater * parent = dynamic_cast<LLFloater *>(getParent()); + if (parent && parent->isDocked()) { const S32 SLOP = 12; if (delta_y <= -SLOP || delta_y >= SLOP) { - ((LLFloater*)getParent())->setDocked(false, false); + parent->setDocked(false, false); return TRUE; } else diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 273ceb4038..c462b2aa52 100755 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -29,7 +29,7 @@ // mini-map floater, etc. #include "linden_common.h" - +#include "llviewereventrecorder.h" #include "llfloater.h" #include "llfocusmgr.h" @@ -528,7 +528,6 @@ LLFloater::~LLFloater() setVisible(false); // We're not visible if we're destroyed storeVisibilityControl(); storeDockStateControl(); - delete mMinimizeSignal; } @@ -642,7 +641,10 @@ void LLFloater::handleVisibilityChange ( BOOL new_visibility ) void LLFloater::openFloater(const LLSD& key) { - llinfos << "Opening floater " << getName() << llendl; + llinfos << "Opening floater " << getName() << " full path: " << getPathname() << llendl; + + LLViewerEventRecorder::instance().logVisibilityChange( getPathname(), getName(), true,"floater"); // Last param is event subtype or empty string + mKey = key; // in case we need to open ourselves again if (getSoundFlags() != SILENT @@ -696,6 +698,7 @@ void LLFloater::openFloater(const LLSD& key) void LLFloater::closeFloater(bool app_quitting) { llinfos << "Closing floater " << getName() << llendl; + LLViewerEventRecorder::instance().logVisibilityChange( getPathname(), getName(), false,"floater"); // Last param is event subtype or empty string if (app_quitting) { LLFloater::sQuitting = true; @@ -1134,7 +1137,11 @@ void LLFloater::handleReshape(const LLRect& new_rect, bool by_user) if (by_user && !getHost()) { - static_cast<LLFloaterView*>(getParent())->adjustToFitScreen(this, !isMinimized()); + LLFloaterView * floaterVp = dynamic_cast<LLFloaterView*>(getParent()); + if (floaterVp) + { + floaterVp->adjustToFitScreen(this, !isMinimized()); + } } // if not minimized, adjust all snapped dependents to new shape @@ -1335,7 +1342,7 @@ void LLFloater::setFocus( BOOL b ) { return; } - LLUICtrl* last_focus = gFocusMgr.getLastFocusForGroup(this); + LLView* last_focus = gFocusMgr.getLastFocusForGroup(this); // a descendent already has focus BOOL child_had_focus = hasFocus(); @@ -1345,7 +1352,8 @@ void LLFloater::setFocus( BOOL b ) if (b) { // only push focused floaters to front of stack if not in midst of ctrl-tab cycle - if (!getHost() && !((LLFloaterView*)getParent())->getCycleMode()) + LLFloaterView * parent = dynamic_cast<LLFloaterView *>(getParent()); + if (!getHost() && parent && !parent->getCycleMode()) { if (!isFrontmost()) { @@ -1543,6 +1551,17 @@ BOOL LLFloater::handleScrollWheel(S32 x, S32 y, S32 clicks) } // virtual +BOOL LLFloater::handleMouseUp(S32 x, S32 y, MASK mask) +{ + lldebugs << "LLFloater::handleMouseUp calling LLPanel (really LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << llendl; + BOOL handled = LLPanel::handleMouseUp(x,y,mask); // Not implemented in LLPanel so this actually calls LLView + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + } + return handled; +} + +// virtual BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) { if( mMinimized ) @@ -1562,7 +1581,11 @@ BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) else { bringToFront( x, y ); - return LLPanel::handleMouseDown( x, y, mask ); + BOOL handled = LLPanel::handleMouseDown( x, y, mask ); + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + } + return handled; } } @@ -1600,7 +1623,7 @@ void LLFloater::bringToFront( S32 x, S32 y ) } else { - LLFloaterView* parent = (LLFloaterView*) getParent(); + LLFloaterView* parent = dynamic_cast<LLFloaterView*>( getParent() ); if (parent) { parent->bringToFront( this ); @@ -1639,7 +1662,11 @@ 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); + LLFloaterView * parent = dynamic_cast<LLFloaterView*>( getParent() ); + if (parent) + { + parent->bringToFront(this, take_focus); + } // Make sure to set the appropriate transparency type (STORM-732). updateTransparency(hasFocus() || getIsChrome() ? TT_ACTIVE : TT_INACTIVE); @@ -1802,7 +1829,7 @@ void LLFloater::onClickClose( LLFloater* self ) self->onClickCloseBtn(); } -void LLFloater::onClickCloseBtn() +void LLFloater::onClickCloseBtn(bool app_quitting) { closeFloater(false); } @@ -1932,6 +1959,7 @@ void LLFloater::drawShadow(LLPanel* panel) void LLFloater::updateTransparency(LLView* view, ETypeTransparency transparency_type) { + if (!view) return; child_list_t children = *view->getChildList(); child_list_t::iterator it = children.begin(); @@ -2368,6 +2396,9 @@ LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLF void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus) { + if (!child) + return; + if (mFrontChild == child) { if (give_focus && !gFocusMgr.childHasKeyboardFocus(child)) @@ -2723,8 +2754,6 @@ void LLFloaterView::refresh() } } -const S32 FLOATER_MIN_VISIBLE_PIXELS = 16; - void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_outside, BOOL snap_in_toolbars/* = false*/) { if (floater->getParent() != this) @@ -2777,11 +2806,29 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out } } + const LLRect& floater_rect = floater->getRect(); + + S32 delta_left = mToolbarLeftRect.notEmpty() ? mToolbarLeftRect.mRight - floater_rect.mRight : 0; + S32 delta_bottom = mToolbarBottomRect.notEmpty() ? mToolbarBottomRect.mTop - floater_rect.mTop : 0; + S32 delta_right = mToolbarRightRect.notEmpty() ? mToolbarRightRect.mLeft - floater_rect.mLeft : 0; + // move window fully onscreen if (floater->translateIntoRect( snap_in_toolbars ? getSnapRect() : gFloaterView->getRect(), allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX )) { floater->clearSnapTarget(); } + else if (delta_left > 0 && floater_rect.mTop < mToolbarLeftRect.mTop && floater_rect.mBottom > mToolbarLeftRect.mBottom) + { + floater->translate(delta_left, 0); + } + else if (delta_bottom > 0 && floater_rect.mLeft > mToolbarBottomRect.mLeft && floater_rect.mRight < mToolbarBottomRect.mRight) + { + floater->translate(0, delta_bottom); + } + else if (delta_right < 0 && floater_rect.mTop < mToolbarRightRect.mTop && floater_rect.mBottom > mToolbarRightRect.mBottom) + { + floater->translate(delta_right, 0); + } } void LLFloaterView::draw() @@ -2827,10 +2874,13 @@ LLFloater *LLFloaterView::getFocusedFloater() const { for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { - LLUICtrl* ctrlp = (*child_it)->isCtrl() ? static_cast<LLUICtrl*>(*child_it) : NULL; - if ( ctrlp && ctrlp->hasFocus() ) + if ((*child_it)->isCtrl()) { - return static_cast<LLFloater *>(ctrlp); + LLFloater* ctrlp = dynamic_cast<LLFloater*>(*child_it); + if ( ctrlp && ctrlp->hasFocus() ) + { + return ctrlp; + } } } return NULL; @@ -2981,6 +3031,25 @@ void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) LLFloaterReg::blockShowFloaters(false); } +void LLFloaterView::setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect) +{ + switch (tb) + { + case LLToolBarEnums::TOOLBAR_LEFT: + mToolbarLeftRect = toolbar_rect; + break; + case LLToolBarEnums::TOOLBAR_BOTTOM: + mToolbarBottomRect = toolbar_rect; + break; + case LLToolBarEnums::TOOLBAR_RIGHT: + mToolbarRightRect = toolbar_rect; + break; + default: + llwarns << "setToolbarRect() passed odd toolbar number " << (S32) tb << llendl; + break; + } +} + void LLFloater::setInstanceName(const std::string& name) { if (name != mInstanceName) diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 59448530d9..12eb3cdbfa 100755 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -32,6 +32,7 @@ #define LL_FLOATER_H #include "llpanel.h" +#include "lltoolbar.h" #include "lluuid.h" //#include "llnotificationsutil.h" #include <set> @@ -289,6 +290,7 @@ public: S32 getHeaderHeight() const { return mHeaderHeight; } virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); virtual BOOL handleMiddleMouseDown(S32 x, S32 y, MASK mask); @@ -389,7 +391,7 @@ protected: void destroy(); // Don't call this directly. You probably want to call closeFloater() - virtual void onClickCloseBtn(); + virtual void onClickCloseBtn(bool app_quitting = false); virtual void updateTitleButtons(); @@ -513,6 +515,8 @@ private: // LLFloaterView // Parent of all floating panels +const S32 FLOATER_MIN_VISIBLE_PIXELS = 16; + class LLFloaterView : public LLUICtrl { public: @@ -571,10 +575,15 @@ public: void setFloaterSnapView(LLHandle<LLView> snap_view) {mSnapView = snap_view; } LLFloater* getFrontmostClosableFloater(); + void setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect); + private: void hiddenFloaterClosed(LLFloater* floater); LLRect mLastSnapRect; + LLRect mToolbarLeftRect; + LLRect mToolbarBottomRect; + LLRect mToolbarRightRect; LLHandle<LLView> mSnapView; BOOL mFocusCycleMode; S32 mSnapOffsetBottom; diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index 724d190307..f03c8d444b 100755 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -469,7 +469,7 @@ void LLFocusMgr::setAppHasFocus(BOOL focus) mAppHasFocus = focus; } -LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const +LLView* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const { if (subtree_root) { @@ -477,7 +477,7 @@ LLUICtrl* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const if (found_it != mImpl->mFocusHistory.end()) { // found last focus for this subtree - return static_cast<LLUICtrl*>(found_it->second.get()); + return found_it->second.get(); } } return NULL; diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index 25ae1d2579..1c7326260c 100755 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -97,7 +97,7 @@ public: void triggerFocusFlash(); BOOL getAppHasFocus() const { return mAppHasFocus; } void setAppHasFocus(BOOL focus); - LLUICtrl* getLastFocusForGroup(LLView* subtree_root) const; + LLView* getLastFocusForGroup(LLView* subtree_root) const; void clearLastFocusForGroup(LLView* subtree_root); // If setKeyboardFocus(NULL) is called, and there is a non-NULL default diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index cf449217f5..f32a52e6c6 100755 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -185,7 +185,7 @@ LLFolderView::LLFolderView(const Params& p) mAutoOpenCandidate = NULL; mAutoOpenTimer.stop(); mKeyboardSelection = FALSE; - mIndentation = p.folder_indentation; + mIndentation = getParentFolder() ? getParentFolder()->getIndentation() + mLocalIndentation : 0; //clear label // go ahead and render root folder as usual @@ -1613,7 +1613,7 @@ void LLFolderView::update() { getFolderViewModel()->getFilter().clearModified(); } - + // automatically show matching items, and select first one if we had a selection if (mNeedsAutoSelect) { @@ -1653,13 +1653,13 @@ void LLFolderView::update() BOOL is_visible = isInVisibleChain(); - // Puts folders/items in proper positions + //Puts folders/items in proper positions // arrange() takes the model filter flag into account and call sort() if necessary (CHUI-849) // It also handles the open/close folder animation - if (is_visible) + if ( is_visible ) { sanitizeSelection(); - if (needsArrange()) + if( needsArrange() ) { S32 height = 0; S32 width = 0; diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 6c147ccc12..aa2343226c 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -307,7 +307,12 @@ std::set<LLFolderViewItem*> LLFolderViewItem::getSelectionList() const // addToFolder() returns TRUE if it succeeds. FALSE otherwise void LLFolderViewItem::addToFolder(LLFolderViewFolder* folder) { - folder->addItem(this); + folder->addItem(this); + + // Compute indentation since parent folder changed + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; } @@ -521,7 +526,7 @@ BOOL LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask ) BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask ) { - static LLCachedControl<S32> drag_and_drop_threshold(*LLUI::sSettingGroups["config"],"DragAndDropDistanceThreshold"); + static LLCachedControl<S32> drag_and_drop_threshold(*LLUI::sSettingGroups["config"],"DragAndDropDistanceThreshold", 3); mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight)); @@ -940,6 +945,11 @@ LLFolderViewFolder::~LLFolderViewFolder( void ) void LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder) { folder->addFolder(this); + + // Compute indentation since parent folder changed + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; } static LLFastTimer::DeclareTimer FTM_ARRANGE("Arrange"); @@ -1109,7 +1119,7 @@ S32 LLFolderViewFolder::arrange( S32* width, S32* height ) BOOL LLFolderViewFolder::needsArrange() { - return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); + return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); } // Passes selection information on to children and record selection diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp index 26d27d1f34..39153977bf 100755 --- a/indra/llui/llkeywords.cpp +++ b/indra/llui/llkeywords.cpp @@ -76,8 +76,11 @@ inline BOOL LLKeywordToken::isTail(const llwchar* s) const LLKeywords::~LLKeywords() { std::for_each(mWordTokenMap.begin(), mWordTokenMap.end(), DeletePairedPointer()); + mWordTokenMap.clear(); std::for_each(mLineTokenList.begin(), mLineTokenList.end(), DeletePointer()); + mLineTokenList.clear(); std::for_each(mDelimiterTokenList.begin(), mDelimiterTokenList.end(), DeletePointer()); + mDelimiterTokenList.clear(); } BOOL LLKeywords::loadFromFile( const std::string& filename ) diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index f854e1785d..6a57158eaa 100755 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -693,8 +693,11 @@ void LLMenuItemTearOffGL::onCommit() { if (getMenu()->getTornOff()) { - LLTearOffMenu* torn_off_menu = (LLTearOffMenu*)(getMenu()->getParent()); - torn_off_menu->closeFloater(); + LLTearOffMenu * torn_off_menu = dynamic_cast<LLTearOffMenu*>(getMenu()->getParent()); + if (torn_off_menu) + { + torn_off_menu->closeFloater(); + } } else { @@ -1097,7 +1100,8 @@ void LLMenuItemBranchGL::setHighlight( BOOL highlight ) BOOL auto_open = getEnabled() && (!branch->getVisible() || branch->getTornOff()); // torn off menus don't open sub menus on hover unless they have focus - if (getMenu()->getTornOff() && !((LLFloater*)getMenu()->getParent())->hasFocus()) + LLFloater * menu_parent = dynamic_cast<LLFloater *>(getMenu()->getParent()); + if (getMenu()->getTornOff() && menu_parent && !menu_parent->hasFocus()) { auto_open = FALSE; } @@ -1118,7 +1122,11 @@ void LLMenuItemBranchGL::setHighlight( BOOL highlight ) { if (branch->getTornOff()) { - ((LLFloater*)branch->getParent())->setFocus(FALSE); + LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent()); + if (branch_parent) + { + branch_parent->setFocus(FALSE); + } branch->clearHoverItem(); } else @@ -1175,11 +1183,19 @@ BOOL LLMenuItemBranchGL::handleKeyHere( KEY key, MASK mask ) BOOL handled = branch->clearHoverItem(); if (branch->getTornOff()) { - ((LLFloater*)branch->getParent())->setFocus(FALSE); + LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent()); + if (branch_parent) + { + branch_parent->setFocus(FALSE); + } } if (handled && getMenu()->getTornOff()) { - ((LLFloater*)getMenu()->getParent())->setFocus(TRUE); + LLFloater * menu_parent = dynamic_cast<LLFloater *>(getMenu()->getParent()); + if (menu_parent) + { + menu_parent->setFocus(TRUE); + } } return handled; } @@ -1219,9 +1235,13 @@ void LLMenuItemBranchGL::openMenu() if (branch->getTornOff()) { - gFloaterView->bringToFront((LLFloater*)branch->getParent()); - // this might not be necessary, as torn off branches don't get focus and hence no highligth - branch->highlightNextItem(NULL); + LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent()); + if (branch_parent) + { + gFloaterView->bringToFront(branch_parent); + // this might not be necessary, as torn off branches don't get focus and hence no highligth + branch->highlightNextItem(NULL); + } } else if( !branch->getVisible() ) { @@ -1348,7 +1368,11 @@ void LLMenuItemBranchDownGL::openMenu( void ) { if (branch->getTornOff()) { - gFloaterView->bringToFront((LLFloater*)branch->getParent()); + LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent()); + if (branch_parent) + { + gFloaterView->bringToFront(branch_parent); + } } else { @@ -1403,7 +1427,11 @@ void LLMenuItemBranchDownGL::setHighlight( BOOL highlight ) { if (branch->getTornOff()) { - ((LLFloater*)branch->getParent())->setFocus(FALSE); + LLFloater * branch_parent = dynamic_cast<LLFloater *>(branch->getParent()); + if (branch_parent) + { + branch_parent->setFocus(FALSE); + } branch->clearHoverItem(); } else @@ -1826,20 +1854,28 @@ BOOL LLMenuGL::jumpKeysActive() { LLMenuItemGL* highlighted_item = getHighlightedItem(); BOOL active = getVisible() && getEnabled(); - if (getTornOff()) - { - // activation of jump keys on torn off menus controlled by keyboard focus - active = active && ((LLFloater*)getParent())->hasFocus(); - } - else + if (active) { - // Are we the terminal active menu? - // Yes, if parent menu item deems us to be active (just being visible is sufficient for top-level menus) - // and we don't have a highlighted menu item pointing to an active sub-menu - active = active && (!getParentMenuItem() || getParentMenuItem()->isActive()) // I have a parent that is active... - && (!highlighted_item || !highlighted_item->isActive()); //... but no child that is active + if (getTornOff()) + { + // activation of jump keys on torn off menus controlled by keyboard focus + LLFloater * parent = dynamic_cast<LLFloater *>(getParent()); + if (parent) + { + active = parent->hasFocus(); + } + } + else + { + // Are we the terminal active menu? + // Yes, if parent menu item deems us to be active (just being visible is sufficient for top-level menus) + // and we don't have a highlighted menu item pointing to an active sub-menu + active = (!getParentMenuItem() || getParentMenuItem()->isActive()) // I have a parent that is active... + && (!highlighted_item || !highlighted_item->isActive()); //... but no child that is active + } } + return active; } @@ -1855,7 +1891,12 @@ BOOL LLMenuGL::isOpen() return TRUE; } // otherwise we are only active if we have keyboard focus - return ((LLFloater*)getParent())->hasFocus(); + LLFloater * parent = dynamic_cast<LLFloater *>(getParent()); + if (parent) + { + return parent->hasFocus(); + } + return FALSE; } else { @@ -2714,7 +2755,11 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa // same as giving focus to it if (!cur_item && getTornOff()) { - ((LLFloater*)getParent())->setFocus(TRUE); + LLFloater * parent = dynamic_cast<LLFloater *>(getParent()); + if (parent) + { + parent->setFocus(TRUE); + } } // Current item position in the items list @@ -2816,7 +2861,11 @@ LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disa // same as giving focus to it if (!cur_item && getTornOff()) { - ((LLFloater*)getParent())->setFocus(TRUE); + LLFloater * parent = dynamic_cast<LLFloater *>(getParent()); + if (parent) + { + parent->setFocus(TRUE); + } } // Current item reverse position from the end of the list diff --git a/indra/llui/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp index 8c2be44904..4d6e189b92 100755 --- a/indra/llui/llmodaldialog.cpp +++ b/indra/llui/llmodaldialog.cpp @@ -34,7 +34,7 @@ #include "llui.h" #include "llwindow.h" #include "llkeyboard.h" - +#include "llmenugl.h" // static std::list<LLModalDialog*> LLModalDialog::sModalStack; @@ -161,6 +161,18 @@ void LLModalDialog::setVisible( BOOL visible ) BOOL LLModalDialog::handleMouseDown(S32 x, S32 y, MASK mask) { + LLView* popup_menu = LLMenuGL::sMenuContainer->getVisibleMenu(); + if (popup_menu != NULL) + { + S32 mx, my; + LLUI::getMousePositionScreen(&mx, &my); + LLRect menu_screen_rc = popup_menu->calcScreenRect(); + if(!menu_screen_rc.pointInRect(mx, my)) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + } + if (mModal) { if (!LLFloater::handleMouseDown(x, y, mask)) @@ -173,6 +185,8 @@ BOOL LLModalDialog::handleMouseDown(S32 x, S32 y, MASK mask) { LLFloater::handleMouseDown(x, y, mask); } + + return TRUE; } @@ -183,6 +197,22 @@ BOOL LLModalDialog::handleHover(S32 x, S32 y, MASK mask) getWindow()->setCursor(UI_CURSOR_ARROW); lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; } + + LLView* popup_menu = LLMenuGL::sMenuContainer->getVisibleMenu(); + if (popup_menu != NULL) + { + S32 mx, my; + LLUI::getMousePositionScreen(&mx, &my); + LLRect menu_screen_rc = popup_menu->calcScreenRect(); + if(menu_screen_rc.pointInRect(mx, my)) + { + S32 local_x = mx - popup_menu->getRect().mLeft; + S32 local_y = my - popup_menu->getRect().mBottom; + popup_menu->handleHover(local_x, local_y, mask); + gFocusMgr.setMouseCapture(NULL); + } + } + return TRUE; } @@ -210,6 +240,7 @@ BOOL LLModalDialog::handleDoubleClick(S32 x, S32 y, MASK mask) BOOL LLModalDialog::handleRightMouseDown(S32 x, S32 y, MASK mask) { + LLMenuGL::sMenuContainer->hideMenus(); childrenHandleRightMouseDown(x, y, mask); return TRUE; } diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 3186c5460a..5c288c3f03 100755 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1206,6 +1206,7 @@ LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything), mIgnoreAllNotifications(false) { + mListener.reset(new LLNotificationsListener(*this)); LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); } diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 3b620084ee..6ac4a98806 100755 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -98,6 +98,8 @@ #include "llrefcount.h" #include "llsdparam.h" +#include "llnotificationslistener.h" + class LLAvatarName; typedef enum e_notification_priority { @@ -978,6 +980,8 @@ private: bool mIgnoreAllNotifications; + boost::scoped_ptr<LLNotificationsListener> mListener; + std::vector<LLNotificationChannelPtr> mDefaultChannels; }; diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp new file mode 100644 index 0000000000..9e8e943ee6 --- /dev/null +++ b/indra/llui/llnotificationslistener.cpp @@ -0,0 +1,359 @@ +/** + * @file llnotificationslistener.cpp + * @author Brad Kittenbrink + * @date 2009-07-08 + * @brief Implementation for llnotificationslistener. + * + * $LicenseInfo:firstyear=2009&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 "llnotificationslistener.h" +#include "llnotifications.h" +#include "llnotificationtemplate.h" +#include "llsd.h" +#include "llui.h" + +LLNotificationsListener::LLNotificationsListener(LLNotifications & notifications) : + LLEventAPI("LLNotifications", + "LLNotifications listener to (e.g.) pop up a notification"), + mNotifications(notifications) +{ + add("requestAdd", + "Add a notification with specified [\"name\"], [\"substitutions\"] and [\"payload\"].\n" + "If optional [\"reply\"] specified, arrange to send user response on that LLEventPump.", + &LLNotificationsListener::requestAdd); + /* add("listChannels", + "Post to [\"reply\"] a map of info on existing channels", + &LLNotificationsListener::listChannels, + LLSD().with("reply", LLSD())); + */ + add("listChannelNotifications", + "Post to [\"reply\"] an array of info on notifications in channel [\"channel\"]", + &LLNotificationsListener::listChannelNotifications, + LLSD().with("reply", LLSD()).with("channel", LLSD())); + add("respond", + "Respond to notification [\"uuid\"] with data in [\"response\"]", + &LLNotificationsListener::respond, + LLSD().with("uuid", LLSD())); + add("cancel", + "Cancel notification [\"uuid\"]", + &LLNotificationsListener::cancel, + LLSD().with("uuid", LLSD())); + add("ignore", + "Ignore future notification [\"name\"]\n" + "(from <notification name= > in notifications.xml)\n" + "according to boolean [\"ignore\"].\n" + "If [\"name\"] is omitted or undefined, [un]ignore all future notifications.\n" + "Note that ignored notifications are not forwarded unless intercepted before\n" + "the \"Ignore\" channel.", + &LLNotificationsListener::ignore); + add("forward", + "Forward to [\"pump\"] future notifications on channel [\"channel\"]\n" + "according to boolean [\"forward\"]. When enabled, only types matching\n" + "[\"types\"] are forwarded, as follows:\n" + "omitted or undefined: forward all notifications\n" + "string: forward only the specific named [sig]type\n" + "array of string: forward any notification matching any named [sig]type.\n" + "When boolean [\"respond\"] is true, we auto-respond to each forwarded\n" + "notification.", + &LLNotificationsListener::forward, + LLSD().with("channel", LLSD())); +} + +// This is here in the .cpp file so we don't need the definition of class +// Forwarder in the header file. +LLNotificationsListener::~LLNotificationsListener() +{ +} + +void LLNotificationsListener::requestAdd(const LLSD& event_data) const +{ + if(event_data.has("reply")) + { + mNotifications.add(event_data["name"], + event_data["substitutions"], + event_data["payload"], + boost::bind(&LLNotificationsListener::NotificationResponder, + this, + event_data["reply"].asString(), + _1, _2 + ) + ); + } + else + { + mNotifications.add(event_data["name"], + event_data["substitutions"], + event_data["payload"]); + } +} + +void LLNotificationsListener::NotificationResponder(const std::string& reply_pump, + const LLSD& notification, + const LLSD& response) const +{ + LLSD reponse_event; + reponse_event["notification"] = notification; + reponse_event["response"] = response; + LLEventPumps::getInstance()->obtain(reply_pump).post(reponse_event); +} +/* +void LLNotificationsListener::listChannels(const LLSD& params) const +{ + LLReqID reqID(params); + LLSD response(reqID.makeResponse()); + for (LLNotifications:: + + + + for (LLNotifications::ChannelMap::const_iterator cmi(mNotifications.mChannels.begin()), + cmend(mNotifications.mChannels.end()); + cmi != cmend; ++cmi) + { + LLSD channelInfo; + channelInfo["parent"] = cmi->second->getParentChannelName(); + response[cmi->first] = channelInfo; + } + LLEventPumps::instance().obtain(params["reply"]).post(response); +} +*/ +void LLNotificationsListener::listChannelNotifications(const LLSD& params) const +{ + LLReqID reqID(params); + LLSD response(reqID.makeResponse()); + LLNotificationChannelPtr channel(mNotifications.getChannel(params["channel"])); + if (channel) + { + LLSD notifications(LLSD::emptyArray()); + for (LLNotificationChannel::Iterator ni(channel->begin()), nend(channel->end()); + ni != nend; ++ni) + { + notifications.append(asLLSD(*ni)); + } + response["notifications"] = notifications; + } + LLEventPumps::instance().obtain(params["reply"]).post(response); +} + +void LLNotificationsListener::respond(const LLSD& params) const +{ + LLNotificationPtr notification(mNotifications.find(params["uuid"])); + if (notification) + { + notification->respond(params["response"]); + } +} + +void LLNotificationsListener::cancel(const LLSD& params) const +{ + LLNotificationPtr notification(mNotifications.find(params["uuid"])); + if (notification) + { + mNotifications.cancel(notification); + } +} + +void LLNotificationsListener::ignore(const LLSD& params) const +{ + // Calling a method named "ignore", but omitting its "ignore" Boolean + // argument, should by default cause something to be ignored. Explicitly + // pass ["ignore"] = false to cancel ignore. + bool ignore = true; + if (params.has("ignore")) + { + ignore = params["ignore"].asBoolean(); + } + // This method can be used to affect either a single notification name or + // all future notifications. The two use substantially different mechanisms. + if (params["name"].isDefined()) + { + // ["name"] was passed: ignore just that notification + LLNotificationTemplatePtr templatep = mNotifications.getTemplate(params["name"]); + if (templatep) + { + templatep->mForm->setIgnored(ignore); + } + } + else + { + // no ["name"]: ignore all future notifications + mNotifications.setIgnoreAllNotifications(ignore); + } +} + +class LLNotificationsListener::Forwarder: public LLEventTrackable +{ + LOG_CLASS(LLNotificationsListener::Forwarder); +public: + Forwarder(LLNotifications& llnotifications, const std::string& channel): + mNotifications(llnotifications), + mRespond(false) + { + // Connect to the specified channel on construction. Because + // LLEventTrackable is a base, we should automatically disconnect when + // destroyed. + LLNotificationChannelPtr channelptr(llnotifications.getChannel(channel)); + if (channelptr) + { + // Insert our processing as a "passed filter" listener. This way + // we get to run before all the "changed" listeners, and we get to + // swipe it (hide it from the other listeners) if desired. + channelptr->connectPassedFilter(boost::bind(&Forwarder::handle, this, _1)); + } + } + + void setPumpName(const std::string& name) { mPumpName = name; } + void setTypes(const LLSD& types) { mTypes = types; } + void setRespond(bool respond) { mRespond = respond; } + +private: + bool handle(const LLSD& notification) const; + bool matchType(const LLSD& filter, const std::string& type) const; + + LLNotifications& mNotifications; + std::string mPumpName; + LLSD mTypes; + bool mRespond; +}; + +void LLNotificationsListener::forward(const LLSD& params) +{ + std::string channel(params["channel"]); + // First decide whether we're supposed to start forwarding or stop it. + // Default to true. + bool forward = true; + if (params.has("forward")) + { + forward = params["forward"].asBoolean(); + } + if (! forward) + { + // This is a request to stop forwarding notifications on the specified + // channel. The rest of the params don't matter. + // Because mForwarders contains scoped_ptrs, erasing the map entry + // DOES delete the heap Forwarder object. Because Forwarder derives + // from LLEventTrackable, destroying it disconnects it from the + // channel. + mForwarders.erase(channel); + return; + } + // From here on, we know we're being asked to start (or modify) forwarding + // on the specified channel. Find or create an appropriate Forwarder. + ForwarderMap::iterator + entry(mForwarders.insert(ForwarderMap::value_type(channel, ForwarderMap::mapped_type())).first); + if (! entry->second) + { + entry->second.reset(new Forwarder(mNotifications, channel)); + } + // Now, whether this Forwarder is brand-new or not, update it with the new + // request info. + Forwarder& fwd(*entry->second); + fwd.setPumpName(params["pump"]); + fwd.setTypes(params["types"]); + fwd.setRespond(params["respond"]); +} + +bool LLNotificationsListener::Forwarder::handle(const LLSD& notification) const +{ + LL_INFOS("LLNotificationsListener") << "handle(" << notification << ")" << LL_ENDL; + if (notification["sigtype"].asString() == "delete") + { + LL_INFOS("LLNotificationsListener") << "ignoring delete" << LL_ENDL; + // let other listeners see the "delete" operation + return false; + } + LLNotificationPtr note(mNotifications.find(notification["id"])); + if (! note) + { + LL_INFOS("LLNotificationsListener") << notification["id"] << " not found" << LL_ENDL; + return false; + } + if (! matchType(mTypes, note->getType())) + { + LL_INFOS("LLNotificationsListener") << "didn't match types " << mTypes << LL_ENDL; + // We're not supposed to intercept this particular notification. Let + // other listeners process it. + return false; + } + LL_INFOS("LLNotificationsListener") << "sending via '" << mPumpName << "'" << LL_ENDL; + // This is a notification we care about. Forward it through specified + // LLEventPump. + LLEventPumps::instance().obtain(mPumpName).post(asLLSD(note)); + // Are we also being asked to auto-respond? + if (mRespond) + { + LL_INFOS("LLNotificationsListener") << "should respond" << LL_ENDL; + note->respond(LLSD::emptyMap()); + // Did that succeed in removing the notification? Only cancel() if + // it's still around -- otherwise we get an LL_ERRS crash! + note = mNotifications.find(notification["id"]); + if (note) + { + LL_INFOS("LLNotificationsListener") << "respond() didn't clear, canceling" << LL_ENDL; + mNotifications.cancel(note); + } + } + // If we've auto-responded to this notification, then it's going to be + // deleted. Other listeners would get the change operation, try to look it + // up and be baffled by lookup failure. So when we auto-respond, suppress + // this notification: don't pass it to other listeners. + return mRespond; +} + +bool LLNotificationsListener::Forwarder::matchType(const LLSD& filter, const std::string& type) const +{ + // Decide whether this notification matches filter: + // undefined: forward all notifications + if (filter.isUndefined()) + { + return true; + } + // array of string: forward any notification matching any named type + if (filter.isArray()) + { + for (LLSD::array_const_iterator ti(filter.beginArray()), tend(filter.endArray()); + ti != tend; ++ti) + { + if (ti->asString() == type) + { + return true; + } + } + // Didn't match any entry in the array + return false; + } + // string: forward only the specific named type + return (filter.asString() == type); +} + +LLSD LLNotificationsListener::asLLSD(LLNotificationPtr note) +{ + LLSD notificationInfo(note->asLLSD()); + // For some reason the following aren't included in LLNotification::asLLSD(). + notificationInfo["summary"] = note->summarize(); + notificationInfo["id"] = note->id(); + notificationInfo["type"] = note->getType(); + notificationInfo["message"] = note->getMessage(); + notificationInfo["label"] = note->getLabel(); + return notificationInfo; +} diff --git a/indra/llui/llnotificationslistener.h b/indra/llui/llnotificationslistener.h new file mode 100644 index 0000000000..f9f7641de6 --- /dev/null +++ b/indra/llui/llnotificationslistener.h @@ -0,0 +1,69 @@ +/** + * @file llnotificationslistener.h + * @author Brad Kittenbrink + * @date 2009-07-08 + * @brief Wrap subset of LLNotifications API in event API for test scripts. + * + * $LicenseInfo:firstyear=2009&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_LLNOTIFICATIONSLISTENER_H +#define LL_LLNOTIFICATIONSLISTENER_H + +#include "lleventapi.h" +#include "llnotificationptr.h" +#include <boost/shared_ptr.hpp> +#include <map> +#include <string> + +class LLNotifications; +class LLSD; + +class LLNotificationsListener : public LLEventAPI +{ +public: + LLNotificationsListener(LLNotifications & notifications); + ~LLNotificationsListener(); + +private: + void requestAdd(LLSD const & event_data) const; + + void NotificationResponder(const std::string& replypump, + const LLSD& notification, + const LLSD& response) const; + + void listChannels(const LLSD& params) const; + void listChannelNotifications(const LLSD& params) const; + void respond(const LLSD& params) const; + void cancel(const LLSD& params) const; + void ignore(const LLSD& params) const; + void forward(const LLSD& params); + + static LLSD asLLSD(LLNotificationPtr); + + class Forwarder; + typedef std::map<std::string, boost::shared_ptr<Forwarder> > ForwarderMap; + ForwarderMap mForwarders; + LLNotifications & mNotifications; +}; + +#endif // LL_LLNOTIFICATIONSLISTENER_H diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index e63b41f97c..17b9b91ba7 100755 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -330,17 +330,16 @@ private: // local static instance for registering a particular panel class template<typename T> -class LLRegisterPanelClassWrapper -: public LLRegisterPanelClass + class LLPanelInjector { public: - // reigister with either the provided builder, or the generic templated builder - LLRegisterPanelClassWrapper(const std::string& tag); + // register with either the provided builder, or the generic templated builder + LLPanelInjector(const std::string& tag); }; template<typename T> -LLRegisterPanelClassWrapper<T>::LLRegisterPanelClassWrapper(const std::string& tag) + LLPanelInjector<T>::LLPanelInjector(const std::string& tag) { LLRegisterPanelClass::instance().addPanelClass(tag,&LLRegisterPanelClass::defaultPanelClassBuilder<T>); } diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index cbcce0ece5..238eae21c2 100755 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -519,7 +519,7 @@ bool LLScrollContainer::addChild(LLView* view, S32 tab_group) void LLScrollContainer::updateScroll() { - if (!mScrolledView) + if (!getVisible() || !mScrolledView) { return; } diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 6e03f604a2..d4bbea0f8e 100755 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -320,7 +320,9 @@ LLScrollListCtrl::~LLScrollListCtrl() delete mSortCallback; std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + mItemList.clear(); std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer()); + mColumns.clear(); } @@ -1855,7 +1857,7 @@ void LLScrollListCtrl::showNameDetails(std::string id, bool is_group) // open the resident's details or the group details std::string sltype = is_group ? "group" : "agent"; std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; - LLUrlAction::clickAction(slurl); + LLUrlAction::clickAction(slurl, true); } void LLScrollListCtrl::copyNameToClipboard(std::string id, bool is_group) diff --git a/indra/llui/llscrolllistitem.cpp b/indra/llui/llscrolllistitem.cpp index 5a1e96ab03..cc7f42e49a 100755 --- a/indra/llui/llscrolllistitem.cpp +++ b/indra/llui/llscrolllistitem.cpp @@ -50,6 +50,7 @@ LLScrollListItem::LLScrollListItem( const Params& p ) LLScrollListItem::~LLScrollListItem() { std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); + mColumns.clear(); } void LLScrollListItem::addColumn(const LLScrollListCell::Params& p) diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index fd98155704..2b9286f663 100755 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -27,7 +27,7 @@ #include "linden_common.h" #include "lltabcontainer.h" - +#include "llviewereventrecorder.h" #include "llfocusmgr.h" #include "lllocalcliprect.h" #include "llrect.h" @@ -281,6 +281,7 @@ LLTabContainer::LLTabContainer(const LLTabContainer::Params& p) LLTabContainer::~LLTabContainer() { std::for_each(mTabList.begin(), mTabList.end(), DeletePointer()); + mTabList.clear(); } //virtual @@ -578,6 +579,11 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) tab_button->setFocus(TRUE); } } + if (handled) { + // Note: May need to also capture local coords right here ? + LLViewerEventRecorder::instance().update_xui(getPathname( )); + } + return handled; } @@ -629,30 +635,33 @@ BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) BOOL handled = FALSE; BOOL has_scroll_arrows = (getMaxScrollPos() > 0) && !getTabsHidden(); + S32 local_x = x - getRect().mLeft; + S32 local_y = y - getRect().mBottom; + if (has_scroll_arrows) { if (mJumpPrevArrowBtn && mJumpPrevArrowBtn->getRect().pointInRect(x, y)) { - S32 local_x = x - mJumpPrevArrowBtn->getRect().mLeft; - S32 local_y = y - mJumpPrevArrowBtn->getRect().mBottom; + local_x = x - mJumpPrevArrowBtn->getRect().mLeft; + local_y = y - mJumpPrevArrowBtn->getRect().mBottom; handled = mJumpPrevArrowBtn->handleMouseUp(local_x, local_y, mask); } else if (mJumpNextArrowBtn && mJumpNextArrowBtn->getRect().pointInRect(x, y)) { - S32 local_x = x - mJumpNextArrowBtn->getRect().mLeft; - S32 local_y = y - mJumpNextArrowBtn->getRect().mBottom; + local_x = x - mJumpNextArrowBtn->getRect().mLeft; + local_y = y - mJumpNextArrowBtn->getRect().mBottom; handled = mJumpNextArrowBtn->handleMouseUp(local_x, local_y, mask); } else if (mPrevArrowBtn && mPrevArrowBtn->getRect().pointInRect(x, y)) { - S32 local_x = x - mPrevArrowBtn->getRect().mLeft; - S32 local_y = y - mPrevArrowBtn->getRect().mBottom; + local_x = x - mPrevArrowBtn->getRect().mLeft; + local_y = y - mPrevArrowBtn->getRect().mBottom; handled = mPrevArrowBtn->handleMouseUp(local_x, local_y, mask); } else if (mNextArrowBtn && mNextArrowBtn->getRect().pointInRect(x, y)) { - S32 local_x = x - mNextArrowBtn->getRect().mLeft; - S32 local_y = y - mNextArrowBtn->getRect().mBottom; + local_x = x - mNextArrowBtn->getRect().mLeft; + local_y = y - mNextArrowBtn->getRect().mBottom; handled = mNextArrowBtn->handleMouseUp(local_x, local_y, mask); } } @@ -676,6 +685,10 @@ BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) } gFocusMgr.setMouseCapture(NULL); } + if (handled) { + // Note: may need to capture local coords here + LLViewerEventRecorder::instance().update_xui(getPathname( )); + } return handled; } @@ -1059,21 +1072,21 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) if (mIsVertical) { - p.name(std::string("vert tab button")); - p.image_unselected(mMiddleTabParams.tab_left_image_unselected); - p.image_selected(mMiddleTabParams.tab_left_image_selected); - p.follows.flags = p.follows.flags() | FOLLOWS_TOP; + p.name("vtab_"+std::string(child->getName())); + p.image_unselected(mMiddleTabParams.tab_left_image_unselected); + p.image_selected(mMiddleTabParams.tab_left_image_selected); + p.follows.flags = p.follows.flags() | FOLLOWS_TOP; } else - { - p.name(std::string(child->getName()) + " tab"); - p.visible(false); - p.image_unselected(tab_img); - p.image_selected(tab_selected_img); - p.follows.flags = p.follows.flags() | (getTabPosition() == TOP ? FOLLOWS_TOP : FOLLOWS_BOTTOM); - // Try to squeeze in a bit more text - p.pad_left( mLabelPadLeft ); - p.pad_right(2); + { + p.name("htab_"+std::string(child->getName())); + p.visible(false); + p.image_unselected(tab_img); + p.image_selected(tab_selected_img); + p.follows.flags = p.follows.flags() | (getTabPosition() == TOP ? FOLLOWS_TOP : FOLLOWS_BOTTOM); + // Try to squeeze in a bit more text + p.pad_left( mLabelPadLeft ); + p.pad_right(2); } // *TODO : It seems wrong not to use p in both cases considering the way p is initialized diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index cc171661ce..4144a42fd6 100755 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -167,6 +167,7 @@ LLTextBase::Params::Params() max_text_length("max_length", 255), font_shadow("font_shadow"), wrap("wrap"), + trusted_content("trusted_content", true), use_ellipses("use_ellipses", false), parse_urls("parse_urls", false), parse_highlights("parse_highlights", false) @@ -211,6 +212,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mLineSpacingPixels(p.line_spacing.pixels), mClip(p.clip), mClipPartial(p.clip_partial && !p.allow_scroll), + mTrustedContent(p.trusted_content), mTrackEnd( p.track_end ), mScrollIndex(-1), mSelectionStart( 0 ), @@ -1029,7 +1031,7 @@ BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) { LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleMouseUp(x, y, mask)) + if (hasMouseCapture() && cur_segment && cur_segment->handleMouseUp(x, y, mask)) { // Did we just click on a link? if (mURLClickSignal @@ -3164,7 +3166,7 @@ BOOL LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) // Only process the click if it's actually in this segment, not to the right of the end-of-line. if(mEditor.getSegmentAtLocalPos(x, y, false) == this) { - LLUrlAction::clickAction(getStyle()->getLinkHREF()); + LLUrlAction::clickAction(getStyle()->getLinkHREF(), mEditor.isContentTrusted()); return TRUE; } } diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index a74e97cac8..3603f55c3f 100755 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -291,7 +291,8 @@ public: parse_urls, parse_highlights, clip, - clip_partial; + clip_partial, + trusted_content; Optional<S32> v_pad, h_pad; @@ -361,6 +362,7 @@ public: bool getWordWrap() { return mWordWrap; } bool getUseEllipses() { return mUseEllipses; } bool truncate(); // returns true of truncation occurred + bool isContentTrusted() {return mTrustedContent;} // TODO: move into LLTextSegment? void createUrlContextMenu(S32 x, S32 y, const std::string &url); // create a popup context menu for the given Url @@ -634,6 +636,7 @@ protected: 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 mTrustedContent; // if false, does not allow to execute SURL links from this editor bool mPlainText; // didn't use Image or Icon segments bool mAutoIndent; S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp index 11cfa1d263..d175204e6d 100755 --- a/indra/llui/lltextbox.cpp +++ b/indra/llui/lltextbox.cpp @@ -60,10 +60,13 @@ BOOL LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask) if (!handled && mClickedCallback) { + handled = TRUE; + } + + if (handled) + { // Route future Mouse messages here preemptively. (Release on mouse up.) gFocusMgr.setMouseCapture( this ); - - handled = TRUE; } return handled; @@ -71,7 +74,7 @@ BOOL LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask) BOOL LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask) { - BOOL handled = FALSE; + BOOL handled = LLTextBase::handleMouseUp(x, y, mask); if (getSoundFlags() & MOUSE_UP) { @@ -93,10 +96,6 @@ BOOL LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask) handled = TRUE; } } - else - { - handled = LLTextBase::handleMouseUp(x, y, mask); - } return handled; } diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 0c16e06109..02c81c0744 100755 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -310,7 +310,7 @@ LLTextEditor::~LLTextEditor() // Scrollbar is deleted by LLView std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); - + mUndoStack.clear(); // context menu is owned by menu holder, not us //delete mContextMenu; } @@ -666,6 +666,14 @@ void LLTextEditor::selectAll() updatePrimary(); } +void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_pos) +{ + setCursorPos(prev_cursor_pos); + startSelection(); + setCursorPos(next_cursor_pos); + endSelection(); +} + BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; @@ -713,7 +721,6 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) setCursorAtLocalPos( x, y, true ); startSelection(); } - gFocusMgr.setMouseCapture( this ); } handled = TRUE; @@ -722,6 +729,10 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) // Delay cursor flashing resetCursorBlink(); + if (handled && !gFocusMgr.getMouseCapture()) + { + gFocusMgr.setMouseCapture( this ); + } return handled; } @@ -1609,7 +1620,7 @@ BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) } } - if (handled) + if (handled && !gFocusMgr.getMouseCapture()) { updatePrimary(); } diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 32b543ec0e..d3b7bc0eb7 100755 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -144,6 +144,8 @@ public: virtual void selectAll(); virtual BOOL canSelectAll() const; + void selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_pos); + virtual bool canLoadOrSaveToFile(); void selectNext(const std::string& search_text_in, BOOL case_insensitive, BOOL wrap = TRUE); diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp index 928e82cb8c..ee82e0403b 100755 --- a/indra/llui/lltoolbar.cpp +++ b/indra/llui/lltoolbar.cpp @@ -118,7 +118,8 @@ LLToolBar::LLToolBar(const LLToolBar::Params& p) mButtonLeaveSignal(NULL), mButtonRemoveSignal(NULL), mDragAndDropTarget(false), - mCaretIcon(NULL) + mCaretIcon(NULL), + mCenterPanel(NULL) { mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_WITH_TEXT] = p.button_icon_and_text; mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_ONLY] = p.button_icon; @@ -200,14 +201,15 @@ void LLToolBar::initFromParams(const LLToolBar::Params& p) center_panel_p.auto_resize = false; center_panel_p.user_resize = false; center_panel_p.mouse_opaque = false; - LLLayoutPanel* center_panel = LLUICtrlFactory::create<LLLayoutPanel>(center_panel_p); - mCenteringStack->addChild(center_panel); + mCenterPanel = LLUICtrlFactory::create<LLCenterLayoutPanel>(center_panel_p); + mCenteringStack->addChild(mCenterPanel); LLPanel::Params button_panel_p(p.button_panel); - button_panel_p.rect = center_panel->getLocalRect(); - button_panel_p.follows.flags = FOLLOWS_BOTTOM|FOLLOWS_LEFT; + button_panel_p.rect = mCenterPanel->getLocalRect(); + button_panel_p.follows.flags = FOLLOWS_BOTTOM|FOLLOWS_LEFT; mButtonPanel = LLUICtrlFactory::create<LLPanel>(button_panel_p); - center_panel->addChild(mButtonPanel); + mCenterPanel->setButtonPanel(mButtonPanel); + mCenterPanel->addChild(mButtonPanel); mCenteringStack->addChild(LLUICtrlFactory::create<LLLayoutPanel>(border_panel_p)); @@ -1242,3 +1244,15 @@ const std::string LLToolBarButton::getToolTip() const return tooltip; } +void LLToolBar::LLCenterLayoutPanel::handleReshape(const LLRect& rect, bool by_user) +{ + LLLayoutPanel::handleReshape(rect, by_user); + + if (!mReshapeCallback.empty()) + { + LLRect r; + localRectToOtherView(mButtonPanel->getRect(), &r, gFloaterView); + r.stretch(FLOATER_MIN_VISIBLE_PIXELS); + mReshapeCallback(mLocationId, r); + } +} diff --git a/indra/llui/lltoolbar.h b/indra/llui/lltoolbar.h index 743951a41f..9e17eaed8b 100755 --- a/indra/llui/lltoolbar.h +++ b/indra/llui/lltoolbar.h @@ -125,6 +125,19 @@ namespace LLToolBarEnums SIDE_TOP, }; + enum EToolBarLocation + { + TOOLBAR_NONE = 0, + TOOLBAR_LEFT, + TOOLBAR_RIGHT, + TOOLBAR_BOTTOM, + + TOOLBAR_COUNT, + + TOOLBAR_FIRST = TOOLBAR_LEFT, + TOOLBAR_LAST = TOOLBAR_BOTTOM, + }; + LLLayoutStack::ELayoutOrientation getOrientation(SideType sideType); } @@ -150,6 +163,30 @@ class LLToolBar { friend class LLToolBarButton; public: + + class LLCenterLayoutPanel : public LLLayoutPanel + { + public: + typedef struct LLLayoutPanel::Params Params; + typedef boost::function<void(LLToolBarEnums::EToolBarLocation tb, const LLRect& rect)> reshape_callback_t; + + virtual ~LLCenterLayoutPanel() {} + /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); + + void setLocationId(LLToolBarEnums::EToolBarLocation id) { mLocationId = id; } + void setReshapeCallback(reshape_callback_t cb) { mReshapeCallback = cb; } + void setButtonPanel(LLPanel * panel) { mButtonPanel = panel; } + + protected: + friend class LLUICtrlFactory; + LLCenterLayoutPanel(const Params& params) : LLLayoutPanel(params), mButtonPanel(NULL) {} + + private: + reshape_callback_t mReshapeCallback; + LLToolBarEnums::EToolBarLocation mLocationId; + LLPanel * mButtonPanel; + }; + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { Mandatory<LLToolBarEnums::ButtonType> button_display_mode; @@ -198,6 +235,7 @@ public: void setHandleDragCallback(tool_handledrag_callback_t cb) { mHandleDragItemCallback = cb; } void setHandleDropCallback(tool_handledrop_callback_t cb) { mHandleDropCallback = cb; } bool isReadOnly() const { return mReadOnly; } + LLCenterLayoutPanel * getCenterLayoutPanel() const { return mCenterPanel; } LLToolBarButton* createButton(const LLCommandId& id); @@ -270,6 +308,7 @@ private: // related widgets LLLayoutStack* mCenteringStack; + LLCenterLayoutPanel* mCenterPanel; LLPanel* mButtonPanel; LLHandle<class LLContextMenu> mPopupMenuHandle; LLHandle<class LLView> mRemoveButtonHandle; diff --git a/indra/llui/llui.h b/indra/llui/llui.h index 0a0e0e164e..0bc4424a8c 100755 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -405,11 +405,6 @@ public: const std::string& comment = "Declared In Code") : LLCachedControl<T>(LLUI::getControlControlGroup(name), name, default_value, comment) {} - - // This constructor will signal an error if the control doesn't exist in the control group - LLUICachedControl(const std::string& name) - : LLCachedControl<T>(LLUI::getControlControlGroup(name), name) - {} }; namespace LLInitParam diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index b9c843e931..1722bf27bd 100755 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -29,7 +29,7 @@ #define LLUICTRL_CPP #include "lluictrl.h" - +#include "llviewereventrecorder.h" #include "llfocusmgr.h" #include "llpanel.h" #include "lluictrlfactory.h" @@ -308,22 +308,40 @@ void LLUICtrl::onMouseLeave(S32 x, S32 y, MASK mask) //virtual BOOL LLUICtrl::handleMouseDown(S32 x, S32 y, MASK mask) { + + lldebugs << "LLUICtrl::handleMouseDown calling LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << llendl; + BOOL handled = LLView::handleMouseDown(x,y,mask); + if (mMouseDownSignal) { (*mMouseDownSignal)(this,x,y,mask); } + lldebugs << "LLUICtrl::handleMousedown - handled is returning as: " << handled << " " << llendl; + + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-56,-56,getPathname()); + } return handled; } //virtual BOOL LLUICtrl::handleMouseUp(S32 x, S32 y, MASK mask) { + + lldebugs << "LLUICtrl::handleMouseUp calling LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << llendl; + BOOL handled = LLView::handleMouseUp(x,y,mask); + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-56,-56,getPathname()); + } if (mMouseUpSignal) { (*mMouseUpSignal)(this,x,y,mask); } + + lldebugs << "LLUICtrl::handleMouseUp - handled for xui " << getPathname() << " - is returning as: " << handled << " " << llendl; + return handled; } diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp index 23e574cb74..12537d9dd1 100755 --- a/indra/llui/llurlaction.cpp +++ b/indra/llui/llurlaction.cpp @@ -87,14 +87,14 @@ void LLUrlAction::executeSLURL(std::string url) { if (sExecuteSLURLCallback) { - sExecuteSLURLCallback(url); + sExecuteSLURLCallback(url ,true); } } -void LLUrlAction::clickAction(std::string url) +void LLUrlAction::clickAction(std::string url, bool trusted_content) { // Try to handle as SLURL first, then http Url - if ( (sExecuteSLURLCallback) && !sExecuteSLURLCallback(url) ) + if ( (sExecuteSLURLCallback) && !sExecuteSLURLCallback(url, trusted_content) ) { if (sOpenURLCallback) { diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h index e731376b95..5f3626490c 100755 --- a/indra/llui/llurlaction.h +++ b/indra/llui/llurlaction.h @@ -66,7 +66,7 @@ public: static void showLocationOnMap(std::string url); /// perform the appropriate action for left-clicking on a Url - static void clickAction(std::string url); + static void clickAction(std::string url, bool trusted_content); /// copy the label for a Url to the clipboard static void copyLabelToClipboard(std::string url); @@ -86,7 +86,7 @@ public: /// specify the callbacks to enable this class's functionality typedef boost::function<void (const std::string&)> url_callback_t; - typedef boost::function<bool(const std::string& url)> execute_url_callback_t; + typedef boost::function<bool(const std::string& url, bool trusted_content)> execute_url_callback_t; static void setOpenURLCallback(url_callback_t cb); static void setOpenURLInternalCallback(url_callback_t cb); diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index b1cc502c4b..840f67968d 100755 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -1067,7 +1067,8 @@ LLUrlEntrySLLabel::LLUrlEntrySLLabel() std::string LLUrlEntrySLLabel::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 LLUrlEntrySLLabel::getUrl(const std::string &string) const diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 3613a40e2c..5ee2169b66 100755 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -48,7 +48,9 @@ #include "lluictrlfactory.h" #include "lltooltip.h" #include "llsdutil.h" - +#include "llsdserialize.h" +#include "llviewereventrecorder.h" +#include "llkeyboard.h" // for ui edit hack #include "llbutton.h" #include "lllineeditor.h" @@ -642,13 +644,33 @@ void LLView::setVisible(BOOL visible) // virtual void LLView::handleVisibilityChange ( BOOL new_visibility ) { + BOOL old_visibility; + BOOL log_visibility_change = LLViewerEventRecorder::instance().getLoggingStatus(); BOOST_FOREACH(LLView* viewp, mChildList) { // only views that are themselves visible will have their overall visibility affected by their ancestors - if (viewp->getVisible()) + old_visibility=viewp->getVisible(); + + if(log_visibility_change) + { + if (old_visibility!=new_visibility) + { + LLViewerEventRecorder::instance().logVisibilityChange( viewp->getPathname(), viewp->getName(), new_visibility,"widget"); + } + } + + if (old_visibility) { viewp->handleVisibilityChange ( new_visibility ); } + + if(log_visibility_change) + { + // Consider changing returns to confirm success and know which widget grabbed it + // For now assume success and log at highest xui possible + // NOTE we log actual state - which may differ if it somehow failed to set visibility + lldebugs << "LLView::handleVisibilityChange - now: " << getVisible() << " xui: " << viewp->getPathname() << " name: " << viewp->getName() << llendl; + } } } @@ -697,6 +719,7 @@ bool LLView::visibleEnabledAndContains(S32 local_x, S32 local_y) && getEnabled(); } +// This is NOT event recording related void LLView::logMouseEvent() { if (sDebugMouseHandling) @@ -743,7 +766,14 @@ LLView* LLView::childrenHandleMouseEvent(const METHOD& method, S32 x, S32 y, XDA if ((viewp->*method)( local_x, local_y, extra ) || (allow_mouse_block && viewp->blockMouseEvent( local_x, local_y ))) { + lldebugs << "LLView::childrenHandleMouseEvent calling updatemouseeventinfo - local_x|global x "<< local_x << " " << x << "local/global y " << local_y << " " << y << llendl; + lldebugs << "LLView::childrenHandleMouseEvent getPathname for viewp result: " << viewp->getPathname() << "for this view: " << getPathname() << llendl; + + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + + // This is NOT event recording related viewp->logMouseEvent(); + return viewp; } } @@ -766,6 +796,7 @@ LLView* LLView::childrenHandleToolTip(S32 x, S32 y, MASK mask) if (viewp->handleToolTip(local_x, local_y, mask) || viewp->blockMouseEvent(local_x, local_y)) { + // This is NOT event recording related viewp->logMouseEvent(); return viewp; } @@ -824,6 +855,7 @@ LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) if (viewp->handleHover(local_x, local_y, mask) || viewp->blockMouseEvent(local_x, local_y)) { + // This is NOT event recording related viewp->logMouseEvent(); return viewp; } @@ -907,10 +939,12 @@ BOOL LLView::handleKey(KEY key, MASK mask, BOOL called_from_parent) if (!handled) { + // For event logging we don't care which widget handles it + // So we capture the key at the end of this function once we know if it was handled handled = handleKeyHere( key, mask ); - if (handled && LLView::sDebugKeys) + if (handled) { - llinfos << "Key handled by " << getName() << llendl; + llwarns << "Key handled by " << getName() << llendl; } } } @@ -958,6 +992,11 @@ BOOL LLView::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) handled = mParentView->handleUnicodeChar(uni_char, FALSE); } + if (handled) + { + LLViewerEventRecorder::instance().logKeyUnicodeEvent(uni_char); + } + return handled; } @@ -987,12 +1026,16 @@ BOOL LLView::hasMouseCapture() BOOL LLView::handleMouseUp(S32 x, S32 y, MASK mask) { - return childrenHandleMouseUp( x, y, mask ) != NULL; + LLView* r = childrenHandleMouseUp( x, y, mask ); + + return (r!=NULL); } BOOL LLView::handleMouseDown(S32 x, S32 y, MASK mask) { - return childrenHandleMouseDown( x, y, mask ) != NULL; + LLView* r= childrenHandleMouseDown(x, y, mask ); + + return (r!=NULL); } BOOL LLView::handleDoubleClick(S32 x, S32 y, MASK mask) @@ -1065,7 +1108,7 @@ LLView* LLView::childrenHandleDoubleClick(S32 x, S32 y, MASK mask) LLView* LLView::childrenHandleMouseUp(S32 x, S32 y, MASK mask) { - return childrenHandleMouseEvent(&LLView::handleMouseUp, x, y, mask); + return childrenHandleMouseEvent(&LLView::handleMouseUp, x, y, mask); } LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) @@ -1271,52 +1314,55 @@ void LLView::reshape(S32 width, S32 height, BOOL called_from_parent) // move child views according to reshape flags BOOST_FOREACH(LLView* viewp, mChildList) { - LLRect child_rect( viewp->mRect ); - - if (viewp->followsRight() && viewp->followsLeft()) - { - child_rect.mRight += delta_width; - } - else if (viewp->followsRight()) + if (viewp != NULL) { - child_rect.mLeft += delta_width; - child_rect.mRight += delta_width; - } - else if (viewp->followsLeft()) - { - // left is 0, don't need to adjust coords - } - else - { - // BUG what to do when we don't follow anyone? - // for now, same as followsLeft - } + LLRect child_rect( viewp->mRect ); - if (viewp->followsTop() && viewp->followsBottom()) - { - child_rect.mTop += delta_height; - } - else if (viewp->followsTop()) - { - child_rect.mTop += delta_height; - child_rect.mBottom += delta_height; - } - else if (viewp->followsBottom()) - { - // bottom is 0, so don't need to adjust coords - } - else - { - // BUG what to do when we don't follow? - // for now, same as bottom - } + if (viewp->followsRight() && viewp->followsLeft()) + { + child_rect.mRight += delta_width; + } + else if (viewp->followsRight()) + { + child_rect.mLeft += delta_width; + child_rect.mRight += delta_width; + } + else if (viewp->followsLeft()) + { + // left is 0, don't need to adjust coords + } + else + { + // BUG what to do when we don't follow anyone? + // for now, same as followsLeft + } - S32 delta_x = child_rect.mLeft - viewp->getRect().mLeft; - S32 delta_y = child_rect.mBottom - viewp->getRect().mBottom; - viewp->translate( delta_x, delta_y ); - if (child_rect.getWidth() != viewp->getRect().getWidth() || child_rect.getHeight() != viewp->getRect().getHeight()) - { - viewp->reshape(child_rect.getWidth(), child_rect.getHeight()); + if (viewp->followsTop() && viewp->followsBottom()) + { + child_rect.mTop += delta_height; + } + else if (viewp->followsTop()) + { + child_rect.mTop += delta_height; + child_rect.mBottom += delta_height; + } + else if (viewp->followsBottom()) + { + // bottom is 0, so don't need to adjust coords + } + else + { + // BUG what to do when we don't follow? + // for now, same as bottom + } + + S32 delta_x = child_rect.mLeft - viewp->getRect().mLeft; + S32 delta_y = child_rect.mBottom - viewp->getRect().mBottom; + viewp->translate( delta_x, delta_y ); + if (child_rect.getWidth() != viewp->getRect().getWidth() || child_rect.getHeight() != viewp->getRect().getHeight()) + { + viewp->reshape(child_rect.getWidth(), child_rect.getHeight()); + } } } } diff --git a/indra/llui/llviewereventrecorder.cpp b/indra/llui/llviewereventrecorder.cpp new file mode 100644 index 0000000000..546a0f5866 --- /dev/null +++ b/indra/llui/llviewereventrecorder.cpp @@ -0,0 +1,296 @@ +/** + * @file llviewereventrecorder.cpp + * @brief Viewer event recording and playback support for mouse and keyboard events + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * + * Copyright (c) 2013, 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 "llviewereventrecorder.h" +#include "llui.h" +#include "llleap.h" + +LLViewerEventRecorder::LLViewerEventRecorder() { + + clear(UNDEFINED); + logEvents = false; + // Remove any previous event log file + std::string old_log_ui_events_to_llsd_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.old"); + LLFile::remove(old_log_ui_events_to_llsd_file); + + + mLogFilename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.llsd"); + LLFile::rename(mLogFilename, old_log_ui_events_to_llsd_file); + +} + + +bool LLViewerEventRecorder::displayViewerEventRecorderMenuItems() { + return LLUI::sSettingGroups["config"]->getBOOL("ShowEventRecorderMenuItems"); +} + + +void LLViewerEventRecorder::setEventLoggingOn() { + if (! mLog.is_open()) { + mLog.open(mLogFilename, llofstream::out); + } + logEvents=true; + lldebugs << "LLViewerEventRecorder::setEventLoggingOn event logging turned on" << llendl; +} + +void LLViewerEventRecorder::setEventLoggingOff() { + logEvents=false; + mLog.flush(); + mLog.close(); + lldebugs << "LLViewerEventRecorder::setEventLoggingOff event logging turned off" << llendl; +} + + + LLViewerEventRecorder::~LLViewerEventRecorder() { + if (mLog.is_open()) { + mLog.close(); + } +} + +void LLViewerEventRecorder::clear_xui() { + xui.clear(); +} + +void LLViewerEventRecorder::clear(S32 r) { + + xui.clear(); + + local_x=r; + local_y=r; + + global_x=r; + global_y=r; + + +} + +void LLViewerEventRecorder::setMouseLocalCoords(S32 x, S32 y) { + local_x=x; + local_y=y; +} + +void LLViewerEventRecorder::setMouseGlobalCoords(S32 x, S32 y) { + global_x=x; + global_y=y; +} + +void LLViewerEventRecorder::updateMouseEventInfo(S32 local_x, S32 local_y, S32 global_x, S32 global_y, std::string mName) { + + LLView * target_view = LLUI::resolvePath(LLUI::getRootView(), xui); + if (! target_view) { + lldebugs << "LLViewerEventRecorder::updateMouseEventInfo - xui path on file at moment is NOT valid - so DO NOT record these local coords" << llendl; + return; + } + lldebugs << "LLViewerEventRecorder::updateMouseEventInfo b4 updatemouseeventinfo - local_x|global x "<< this->local_x << " " << this->global_x << "local/global y " << this->local_y << " " << this->global_y << " mname: " << mName << " xui: " << xui << llendl; + + + if (this->local_x < 1 && this->local_y<1 && local_x && local_y) { + this->local_x=local_x; + this->local_y=local_y; + } + this->global_x=global_x; + this->global_y=global_y; + + // ONLY record deepest xui path for hierarchy searches - or first/only xui for floaters/panels reached via mouse captor - and llmousehandler + if (mName!="" && mName!="/" && xui=="") { + // xui=std::string("/")+mName+xui; + //xui=mName+xui; + xui = mName; // TODO review confirm we never call with partial path - also cAN REMOVE CHECK FOR "" - ON OTHER HAND IT'S PRETTY HARMLESS + } + + lldebugs << "LLViewerEventRecorder::updateMouseEventInfo after updatemouseeventinfo - local_x|global x "<< this->local_x << " " << this->global_x << "local/global y " << this->local_y << " " << this->global_y << " mname: " << mName << " xui: " << xui << llendl; +} + +void LLViewerEventRecorder::logVisibilityChange(std::string xui, std::string name, BOOL visibility, std::string event_subtype) { + + LLSD event=LLSD::emptyMap(); + + event.insert("event",LLSD(std::string("visibility"))); + + if (visibility) { + event.insert("visibility",LLSD(true)); + } else { + event.insert("visibility",LLSD(false)); + } + + if (event_subtype!="") { + event.insert("event_subtype", LLSD(event_subtype)); + } + + if(name!="") { + event.insert("name",LLSD(name)); + } + + if (xui!="") { + event.insert("path",LLSD(xui)); + } + + event.insert("timestamp",LLSD(LLDate::now().asString())); + recordEvent(event); +} + + +std::string LLViewerEventRecorder::get_xui() { + return xui; +} +void LLViewerEventRecorder::update_xui(std::string xui) { + if (xui!="" && this->xui=="" ) { + lldebugs << "LLViewerEventRecorder::update_xui to " << xui << llendl; + this->xui=xui; + } else { + lldebugs << "LLViewerEventRecorder::update_xui called with empty string" << llendl; + } +} + +void LLViewerEventRecorder::logKeyEvent(KEY key, MASK mask) { + + // NOTE: Event recording only logs keydown events - the viewer itself hides keyup events at a fairly low level in the code and does not appear to care about them anywhere + + LLSD event = LLSD::emptyMap(); + + event.insert("event",LLSD("type")); + + // keysym ...or + // keycode...or + // char + event.insert("keysym",LLSD(LLKeyboard::stringFromKey(key))); + + // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps + // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might + // break the test script and it would be useful to have more context to make these sorts of edits safer + + // TODO replace this with a call which extracts to an array of names of masks (just like vita expects during playback) + // This is looking more and more like an object is a good idea, for this part a handy method call to setMask(mask) would be nice :-) + // call the func - llkeyboard::llsdStringarrayFromMask + + LLSD key_mask=LLSD::emptyArray(); + + if (mask & MASK_CONTROL) { key_mask.append(LLSD("CTL")); } // Mac command key - has code of 0x1 in llcommon/indra_contstants + if (mask & MASK_ALT) { key_mask.append(LLSD("ALT")); } + if (mask & MASK_SHIFT) { key_mask.append(LLSD("SHIFT")); } + if (mask & MASK_MAC_CONTROL) { key_mask.append(LLSD("MAC_CONTROL")); } + + event.insert("mask",key_mask); + event.insert("timestamp",LLSD(LLDate::now().asString())); + + // Although vita has keyDown and keyUp requests it does not have type as a high-level concept + // (maybe it should) - instead it has a convenience method that generates the keydown and keyup events + // Here we will use "type" as our event type + + lldebugs << "LLVIewerEventRecorder::logKeyEvent Serialized LLSD for event " << event.asString() << "\n" << llendl; + + + //lldebugs << "[VITA] key_name: " << LLKeyboard::stringFromKey(key) << "mask: "<< mask << "handled by " << getName() << llendl; + lldebugs << "LLVIewerEventRecorder::logKeyEvent key_name: " << LLKeyboard::stringFromKey(key) << "mask: "<< mask << llendl; + + + recordEvent(event); + +} + +void LLViewerEventRecorder::playbackRecording() { + + LLSD LeapCommand; + + // ivita sets this on startup, it also sends commands to the viewer to make start, stop, and playback menu items visible in viewer + LeapCommand =LLUI::sSettingGroups["config"]->getLLSD("LeapPlaybackEventsCommand"); + + lldebugs << "[VITA] launching playback - leap command is: " << LLSDXMLStreamer(LeapCommand) << llendl; + LLLeap::create("", LeapCommand, false); // exception=false + +} + + +void LLViewerEventRecorder::recordEvent(LLSD event) { + lldebugs << "LLViewerEventRecorder::recordEvent event written to log: " << LLSDXMLStreamer(event) << llendl; + mLog << event << std::endl; + +} +void LLViewerEventRecorder::logKeyUnicodeEvent(llwchar uni_char) { + if (! logEvents) return; + + // Note: keyUp is not captured since the viewer seems to not care about keyUp events + + LLSD event=LLSD::emptyMap(); + + event.insert("timestamp",LLSD(LLDate::now().asString())); + + + // keysym ...or + // keycode...or + // char + + lldebugs << "Wrapped in conversion to wstring " << wstring_to_utf8str(LLWString( 1, uni_char)) << "\n" << llendl; + + event.insert("char", + LLSD( wstring_to_utf8str(LLWString( 1,uni_char)) ) + ); + + // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps + // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might + // break the test script and it would be useful to have more context to make these sorts of edits safer + + // TODO need to consider mask keys too? Doesn't seem possible - at least not easily at this point + + event.insert("event",LLSD("keyDown")); + + lldebugs << "[VITA] unicode key: " << uni_char << llendl; + lldebugs << "[VITA] dumpxml " << LLSDXMLStreamer(event) << "\n" << llendl; + + + recordEvent(event); + +} + +void LLViewerEventRecorder::logMouseEvent(std::string button_state,std::string button_name) +{ + if (! logEvents) return; + + LLSD event=LLSD::emptyMap(); + + event.insert("event",LLSD(std::string("mouse"+ button_state))); + event.insert("button",LLSD(button_name)); + if (xui!="") { + event.insert("path",LLSD(xui)); + } + + if (local_x>0 && local_y>0) { + event.insert("local_x",LLSD(local_x)); + event.insert("local_y",LLSD(local_y)); + } + + if (global_x>0 && global_y>0) { + event.insert("global_x",LLSD(global_x)); + event.insert("global_y",LLSD(global_y)); + } + event.insert("timestamp",LLSD(LLDate::now().asString())); + recordEvent(event); + + + clear(UNDEFINED); + + +} diff --git a/indra/llui/llviewereventrecorder.h b/indra/llui/llviewereventrecorder.h new file mode 100644 index 0000000000..375efcc3de --- /dev/null +++ b/indra/llui/llviewereventrecorder.h @@ -0,0 +1,103 @@ +/** + * @file llviewereventrecorder.h + * @brief Viewer event recording and playback support for mouse and keyboard events + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * + * Copyright (c) 2013, 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_VIEWER_EVENT_RECORDER +#define LL_VIEWER_EVENT_RECORDER + + +#include "linden_common.h" + +#include "lldir.h" +#include "llsd.h" +#include "llfile.h" +#include "llvfile.h" +#include "lldate.h" +#include "llsdserialize.h" +#include "llkeyboard.h" +#include "llstring.h" + +#include <sstream> + +#include "llsingleton.h" // includes llerror which we need here so we can skip the include here + +class LLViewerEventRecorder : public LLSingleton<LLViewerEventRecorder> +{ + + public: + + LLViewerEventRecorder(); // TODO Protect constructor better if we can (not happy in private section) - could add a factory... - we are singleton + ~LLViewerEventRecorder(); + + + void updateMouseEventInfo(S32 local_x,S32 local_y, S32 global_x, S32 global_y, std::string mName); + void setMouseLocalCoords(S32 x,S32 y); + void setMouseGlobalCoords(S32 x,S32 y); + + void logMouseEvent(std::string button_state, std::string button_name ); + void logKeyEvent(KEY key, MASK mask); + void logKeyUnicodeEvent(llwchar uni_char); + + void logVisibilityChange(std::string xui, std::string name, BOOL visibility, std::string event_subtype); + + void clear_xui(); + std::string get_xui(); + void update_xui(std::string xui); + + bool getLoggingStatus(){return logEvents;}; + void setEventLoggingOn(); + void setEventLoggingOff(); + + void playbackRecording(); + + bool displayViewerEventRecorderMenuItems(); + + + protected: + // On if we wish to log events at the moment - toggle via Develop/Recorder submenu + bool logEvents; + + std::string mLogFilename; + llofstream mLog; + + + private: + + // Mouse event info + S32 global_x; + S32 global_y; + S32 local_x; + S32 local_y; + + // XUI path of UI element + std::string xui; + + // Actually write the event out to llsd log file + void recordEvent(LLSD event); + + void clear(S32 r); + + static const S32 UNDEFINED=-1; +}; +#endif |