/** * @file lltoolbar.cpp * @author Richard Nelson * @brief User customizable toolbar class * * $LicenseInfo:firstyear=2011&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2011, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "lltoolbar.h" #include "llcommandmanager.h" #include "llmenugl.h" #include "lltrans.h" #include "llinventory.h" #include "lliconctrl.h" // uncomment this and remove the one in llui.cpp when there is an external reference to this translation unit // thanks, MSVC! //static LLDefaultChildRegistry::Register r1("toolbar"); namespace LLToolBarEnums { LLView::EOrientation getOrientation(SideType sideType) { LLView::EOrientation orientation = LLLayoutStack::HORIZONTAL; if ((sideType == SIDE_LEFT) || (sideType == SIDE_RIGHT)) { orientation = LLLayoutStack::VERTICAL; } return orientation; } } using namespace LLToolBarEnums; namespace LLInitParam { void TypeValues::declareValues() { declare("icons_with_text", BTNTYPE_ICONS_WITH_TEXT); declare("icons_only", BTNTYPE_ICONS_ONLY); } void TypeValues::declareValues() { declare("bottom", SIDE_BOTTOM); declare("left", SIDE_LEFT); declare("right", SIDE_RIGHT); declare("top", SIDE_TOP); } } LLToolBar::Params::Params() : button_display_mode("button_display_mode"), commands("command"), side("side", SIDE_TOP), button_icon("button_icon"), button_icon_and_text("button_icon_and_text"), read_only("read_only", false), wrap("wrap", true), pad_left("pad_left"), pad_top("pad_top"), pad_right("pad_right"), pad_bottom("pad_bottom"), pad_between("pad_between"), min_girth("min_girth"), button_panel("button_panel") {} LLToolBar::LLToolBar(const LLToolBar::Params& p) : LLUICtrl(p), mReadOnly(p.read_only), mButtonType(p.button_display_mode), mSideType(p.side), mWrap(p.wrap), mNeedsLayout(false), mModified(false), mButtonPanel(NULL), mCenteringStack(NULL), mPadLeft(p.pad_left), mPadRight(p.pad_right), mPadTop(p.pad_top), mPadBottom(p.pad_bottom), mPadBetween(p.pad_between), mMinGirth(p.min_girth), mPopupMenuHandle(), mRightMouseTargetButton(NULL), mStartDragItemCallback(NULL), mHandleDragItemCallback(NULL), mHandleDropCallback(NULL), mButtonAddSignal(NULL), mButtonEnterSignal(NULL), mButtonLeaveSignal(NULL), mButtonRemoveSignal(NULL), mDragAndDropTarget(false), mCaretIcon(NULL), mCenterPanel(NULL) { mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_WITH_TEXT] = p.button_icon_and_text; mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_ONLY] = p.button_icon; } LLToolBar::~LLToolBar() { auto menu = mPopupMenuHandle.get(); if (menu) { menu->die(); mPopupMenuHandle.markDead(); } delete mButtonAddSignal; delete mButtonEnterSignal; delete mButtonLeaveSignal; delete mButtonRemoveSignal; } void LLToolBar::createContextMenu() { if (!mPopupMenuHandle.get()) { // Setup bindings specific to this instance for the context menu options LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commit_reg; commit_reg.add("Toolbars.EnableSetting", boost::bind(&LLToolBar::onSettingEnable, this, _2)); commit_reg.add("Toolbars.RemoveSelectedCommand", boost::bind(&LLToolBar::onRemoveSelectedCommand, this)); LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_reg; enable_reg.add("Toolbars.CheckSetting", boost::bind(&LLToolBar::isSettingChecked, this, _2)); // Create the context menu llassert(LLMenuGL::sMenuContainer != NULL); LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile("menu_toolbars.xml", LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); if (menu) { menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); mPopupMenuHandle = menu->getHandle(); mRemoveButtonHandle = menu->getChild("Remove button")->getHandle(); } else { LL_WARNS() << "Unable to load toolbars context menu." << LL_ENDL; } } if (mRemoveButtonHandle.get()) { // Disable/Enable the "Remove button" menu item depending on whether or not a button was clicked mRemoveButtonHandle.get()->setEnabled(mRightMouseTargetButton != NULL); } } void LLToolBar::initFromParams(const LLToolBar::Params& p) { // Initialize the base object LLUICtrl::initFromParams(p); LLView::EOrientation orientation = getOrientation(p.side); LLLayoutStack::Params centering_stack_p; centering_stack_p.name = "centering_stack"; centering_stack_p.rect = getLocalRect(); centering_stack_p.follows.flags = FOLLOWS_ALL; centering_stack_p.orientation = orientation; centering_stack_p.mouse_opaque = false; mCenteringStack = LLUICtrlFactory::create(centering_stack_p); addChild(mCenteringStack); LLLayoutPanel::Params border_panel_p; border_panel_p.name = "border_panel"; border_panel_p.rect = getLocalRect(); border_panel_p.auto_resize = true; border_panel_p.user_resize = false; border_panel_p.mouse_opaque = false; mCenteringStack->addChild(LLUICtrlFactory::create(border_panel_p)); LLLayoutPanel::Params center_panel_p; center_panel_p.name = "center_panel"; center_panel_p.rect = getLocalRect(); center_panel_p.auto_resize = false; center_panel_p.user_resize = false; center_panel_p.mouse_opaque = false; mCenterPanel = LLUICtrlFactory::create(center_panel_p); mCenteringStack->addChild(mCenterPanel); LLPanel::Params button_panel_p(p.button_panel); button_panel_p.rect = mCenterPanel->getLocalRect(); button_panel_p.follows.flags = FOLLOWS_BOTTOM|FOLLOWS_LEFT; mButtonPanel = LLUICtrlFactory::create(button_panel_p); mCenterPanel->setButtonPanel(mButtonPanel); mCenterPanel->addChild(mButtonPanel); mCenteringStack->addChild(LLUICtrlFactory::create(border_panel_p)); for (const auto& id : p.commands) { addCommand(id); } mNeedsLayout = true; } bool LLToolBar::addCommand(const LLCommandId& commandId, int rank) { LLCommand * command = LLCommandManager::instance().getCommand(commandId); if (!command) return false; // Create the button and do the things that don't need ordering LLToolBarButton* button = createButton(commandId); mButtonPanel->addChild(button); mButtonMap.insert(std::make_pair(commandId.uuid(), button)); // Insert the command and button in the right place in their respective lists if ((rank >= mButtonCommands.size()) || (rank == RANK_NONE)) { // In that case, back load mButtonCommands.push_back(command->id()); mButtons.push_back(button); } else { // Insert in place: iterate to the right spot... std::list::iterator it_button = mButtons.begin(); command_id_list_t::iterator it_command = mButtonCommands.begin(); while (rank > 0) { ++it_button; ++it_command; rank--; } // ...then insert mButtonCommands.insert(it_command, command->id()); mButtons.insert(it_button,button); } mNeedsLayout = true; updateLayoutAsNeeded(); if (mButtonAddSignal) { (*mButtonAddSignal)(button); } return true; } // Remove a command from the list // Returns the rank of the command in the original list so that doing addCommand(id,rank) right after // a removeCommand(id) would leave the list unchanged. // Returns RANK_NONE if the command is not found in the list int LLToolBar::removeCommand(const LLCommandId& commandId) { if (!hasCommand(commandId)) return RANK_NONE; // First erase the map record command_id_map::iterator it = mButtonMap.find(commandId.uuid()); mButtonMap.erase(it); // Now iterate on the commands and buttons to identify the relevant records int rank = 0; std::list::iterator it_button = mButtons.begin(); command_id_list_t::iterator it_command = mButtonCommands.begin(); while (*it_command != commandId) { ++it_button; ++it_command; ++rank; } if (mButtonRemoveSignal) { (*mButtonRemoveSignal)(*it_button); } // Delete the button and erase the command and button records delete (*it_button); mButtonCommands.erase(it_command); mButtons.erase(it_button); mNeedsLayout = true; return rank; } void LLToolBar::clearCommandsList() { // Clears the commands list mButtonCommands.clear(); // This will clear the buttons createButtons(); } bool LLToolBar::hasCommand(const LLCommandId& commandId) const { if (commandId != LLCommandId::null) { command_id_map::const_iterator it = mButtonMap.find(commandId.uuid()); return (it != mButtonMap.end()); } return false; } bool LLToolBar::enableCommand(const LLCommandId& commandId, bool enabled) { LLButton * command_button = NULL; if (commandId != LLCommandId::null) { command_id_map::iterator it = mButtonMap.find(commandId.uuid()); if (it != mButtonMap.end()) { command_button = it->second; command_button->setEnabled(enabled); } } return (command_button != NULL); } bool LLToolBar::stopCommandInProgress(const LLCommandId& commandId) { // // Note from Leslie: // // This implementation was largely put in place to handle EXP-1348 which is related to // dragging and dropping the "speak" button. The "speak" button can be in one of two // modes, i.e., either a toggle action or a push-to-talk action. Because of this it // responds to mouse down and mouse up in different ways, based on which behavior the // button is currently set to obey. This was the simplest way of getting the button // to turn off the microphone for both behaviors without risking duplicate state. // LLToolBarButton * command_button = NULL; if (commandId != LLCommandId::null) { LLCommand* command = LLCommandManager::instance().getCommand(commandId); llassert(command); // If this command has an explicit function for execution stop if (command->executeStopFunctionName().length() > 0) { command_id_map::iterator it = mButtonMap.find(commandId.uuid()); if (it != mButtonMap.end()) { command_button = it->second; llassert(command_button->mIsRunningSignal); // Check to see if it is running if ((*command_button->mIsRunningSignal)(command_button, command->isRunningParameters())) { // Trigger an additional button commit, which calls mouse down, mouse up and commit command_button->onCommit(); } } } } return (command_button != NULL); } bool LLToolBar::flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing/* = false */) { LLButton * command_button = NULL; if (commandId != LLCommandId::null) { command_id_map::iterator it = mButtonMap.find(commandId.uuid()); if (it != mButtonMap.end()) { command_button = it->second; command_button->setFlashing((bool)(flash),(bool)(force_flashing)); } } return (command_button != NULL); } bool LLToolBar::handleRightMouseDown(S32 x, S32 y, MASK mask) { LLRect button_panel_rect; mButtonPanel->localRectToOtherView(mButtonPanel->getLocalRect(), &button_panel_rect, this); bool handle_it_here = !mReadOnly && button_panel_rect.pointInRect(x, y); if (handle_it_here) { // Determine which button the mouse was over during the click in case the context menu action // is intended to affect the button. mRightMouseTargetButton = NULL; for (LLToolBarButton* button : mButtons) { LLRect button_rect; button->localRectToOtherView(button->getLocalRect(), &button_rect, this); if (button_rect.pointInRect(x, y)) { mRightMouseTargetButton = button; break; } } createContextMenu(); LLContextMenu * menu = (LLContextMenu *) mPopupMenuHandle.get(); if (menu) { menu->show(x, y); LLMenuGL::showPopup(this, menu, x, y); } } return handle_it_here; } bool LLToolBar::isSettingChecked(const LLSD& userdata) { bool retval = false; const std::string setting_name = userdata.asString(); if (setting_name == "icons_with_text") { retval = (mButtonType == BTNTYPE_ICONS_WITH_TEXT); } else if (setting_name == "icons_only") { retval = (mButtonType == BTNTYPE_ICONS_ONLY); } return retval; } void LLToolBar::onSettingEnable(const LLSD& userdata) { llassert(!mReadOnly); const std::string setting_name = userdata.asString(); if (setting_name == "icons_with_text") { setButtonType(BTNTYPE_ICONS_WITH_TEXT); } else if (setting_name == "icons_only") { setButtonType(BTNTYPE_ICONS_ONLY); } } void LLToolBar::onRemoveSelectedCommand() { llassert(!mReadOnly); if (mRightMouseTargetButton) { removeCommand(mRightMouseTargetButton->getCommandId()); mRightMouseTargetButton = NULL; } } void LLToolBar::setButtonType(LLToolBarEnums::ButtonType button_type) { bool regenerate_buttons = (mButtonType != button_type); mButtonType = button_type; if (regenerate_buttons) { createButtons(); } } void LLToolBar::resizeButtonsInRow(std::vector& buttons_in_row, S32 max_row_girth) { // make buttons in current row all same girth for (LLToolBarButton* button : buttons_in_row) { if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) { button->reshape(button->mWidthRange.clamp(button->getRect().getWidth()), max_row_girth); } else // VERTICAL { button->reshape(max_row_girth, button->getRect().getHeight()); } } } // Returns the position of the coordinates as a rank in the button list. // The rank is the position a tool dropped in (x,y) would assume in the button list. // The returned value is between 0 and mButtons.size(), 0 being the first element to the left // (or top) and mButtons.size() the last one to the right (or bottom). // Various drag data are stored in the toolbar object though are not exposed outside (and shouldn't). int LLToolBar::getRankFromPosition(S32 x, S32 y) { if (mButtons.empty()) { return RANK_NONE; } int rank = 0; // Convert the toolbar coord into button panel coords LLView::EOrientation orientation = getOrientation(mSideType); S32 button_panel_x = 0; S32 button_panel_y = 0; localPointToOtherView(x, y, &button_panel_x, &button_panel_y, mButtonPanel); S32 dx = x - button_panel_x; S32 dy = y - button_panel_y; // Simply compare the passed coord with the buttons outbound box + padding std::list::iterator it_button = mButtons.begin(); std::list::iterator end_button = mButtons.end(); LLRect button_rect; while (it_button != end_button) { button_rect = (*it_button)->getRect(); S32 point_x = button_rect.mRight + mPadRight; S32 point_y = button_rect.mBottom - mPadBottom; if ((button_panel_x < point_x) && (button_panel_y > point_y)) { break; } rank++; ++it_button; } // Update the passed coordinates to the hit button relevant corner // (different depending on toolbar orientation) if (rank < mButtons.size()) { if (orientation == LLLayoutStack::HORIZONTAL) { // Horizontal S32 mid_point = (button_rect.mRight + button_rect.mLeft) / 2; if (button_panel_x < mid_point) { mDragx = button_rect.mLeft - mPadLeft; mDragy = button_rect.mTop + mPadTop; } else { rank++; mDragx = button_rect.mRight + mPadRight - 1; mDragy = button_rect.mTop + mPadTop; } } else { // Vertical S32 mid_point = (button_rect.mTop + button_rect.mBottom) / 2; if (button_panel_y > mid_point) { mDragx = button_rect.mLeft - mPadLeft; mDragy = button_rect.mTop + mPadTop; } else { rank++; mDragx = button_rect.mLeft - mPadLeft; mDragy = button_rect.mBottom - mPadBottom + 1; } } } else { // We hit passed the end of the list so put the insertion point at the end if (orientation == LLLayoutStack::HORIZONTAL) { mDragx = button_rect.mRight + mPadRight; mDragy = button_rect.mTop + mPadTop; } else { mDragx = button_rect.mLeft - mPadLeft; mDragy = button_rect.mBottom - mPadBottom; } } // Update the "girth" of the caret, i.e. the width or height (depending of orientation) if (orientation == LLLayoutStack::HORIZONTAL) { mDragGirth = button_rect.getHeight() + mPadBottom + mPadTop; } else { mDragGirth = button_rect.getWidth() + mPadLeft + mPadRight; } // The delta account for the coord model change (i.e. convert back to toolbar coord) mDragx += dx; mDragy += dy; return rank; } int LLToolBar::getRankFromPosition(const LLCommandId& id) { if (!hasCommand(id)) { return RANK_NONE; } int rank = 0; std::list::iterator it_button = mButtons.begin(); std::list::iterator end_button = mButtons.end(); while (it_button != end_button) { if ((*it_button)->mId == id) { break; } rank++; ++it_button; } return rank; } void LLToolBar::updateLayoutAsNeeded() { if (!mNeedsLayout) return; LLView::EOrientation orientation = getOrientation(mSideType); // our terminology for orientation-agnostic layout is such that // length refers to a distance in the direction we stack the buttons // and girth refers to a distance in the direction buttons wrap S32 max_row_girth = 0; S32 max_row_length = 0; S32 max_length; S32 cur_start; S32 cur_row ; S32 row_pad_start; S32 row_pad_end; S32 girth_pad_end; S32 row_running_length; if (orientation == LLLayoutStack::HORIZONTAL) { max_length = getRect().getWidth() - mPadLeft - mPadRight; row_pad_start = mPadLeft; row_pad_end = mPadRight; cur_row = mPadTop; girth_pad_end = mPadBottom; } else // VERTICAL { max_length = getRect().getHeight() - mPadTop - mPadBottom; row_pad_start = mPadTop; row_pad_end = mPadBottom; cur_row = mPadLeft; girth_pad_end = mPadRight; } row_running_length = row_pad_start; cur_start = row_pad_start; LLRect panel_rect = mButtonPanel->getLocalRect(); std::vector buttons_in_row; for (LLToolBarButton* button : mButtons) { button->reshape(button->mWidthRange.getMin(), button->mDesiredHeight); button->autoResize(); S32 button_clamped_width = button->mWidthRange.clamp(button->getRect().getWidth()); S32 button_length = (orientation == LLLayoutStack::HORIZONTAL) ? button_clamped_width : button->getRect().getHeight(); S32 button_girth = (orientation == LLLayoutStack::HORIZONTAL) ? button->getRect().getHeight() : button_clamped_width; // wrap if needed if (mWrap && row_running_length + button_length > max_length // out of room... && cur_start != row_pad_start) // ...and not first button in row { if (orientation == LLLayoutStack::VERTICAL) { // row girth (width in this case) is clamped to allowable button widths max_row_girth = button->mWidthRange.clamp(max_row_girth); } // make buttons in current row all same girth resizeButtonsInRow(buttons_in_row, max_row_girth); buttons_in_row.clear(); max_row_length = llmax(max_row_length, row_running_length); row_running_length = row_pad_start; cur_start = row_pad_start; cur_row += max_row_girth + mPadBetween; max_row_girth = 0; } LLRect button_rect; if (orientation == LLLayoutStack::HORIZONTAL) { button_rect.setLeftTopAndSize(cur_start, panel_rect.mTop - cur_row, button_clamped_width, button->getRect().getHeight()); } else // VERTICAL { button_rect.setLeftTopAndSize(cur_row, panel_rect.mTop - cur_start, button_clamped_width, button->getRect().getHeight()); } button->setShape(button_rect); buttons_in_row.push_back(button); row_running_length += button_length + mPadBetween; cur_start = row_running_length; max_row_girth = llmax(button_girth, max_row_girth); } // final resizing in "girth" direction S32 total_girth = cur_row // current row position... + max_row_girth // ...incremented by size of final row... + girth_pad_end; // ...plus padding reserved on end total_girth = llmax(total_girth,mMinGirth); max_row_length = llmax(max_row_length, row_running_length - mPadBetween + row_pad_end); resizeButtonsInRow(buttons_in_row, max_row_girth); // grow and optionally shift toolbar to accommodate buttons if (orientation == LLLayoutStack::HORIZONTAL) { if (mSideType == SIDE_TOP) { // shift down to maintain top edge translate(0, getRect().getHeight() - total_girth); } reshape(getRect().getWidth(), total_girth); mButtonPanel->reshape(max_row_length, total_girth); } else // VERTICAL { if (mSideType == SIDE_RIGHT) { // shift left to maintain right edge translate(getRect().getWidth() - total_girth, 0); } reshape(total_girth, getRect().getHeight()); mButtonPanel->reshape(total_girth, max_row_length); } // make parent fit button panel mButtonPanel->getParent()->setShape(mButtonPanel->getLocalRect()); // re-center toolbar buttons mCenteringStack->updateLayout(); if (!mButtons.empty()) { mButtonPanel->setVisible(true); mButtonPanel->setMouseOpaque(true); } // don't clear flag until after we've resized ourselves, to avoid laying out every frame mNeedsLayout = false; } void LLToolBar::draw() { if (mButtons.empty()) { mButtonPanel->setVisible(false); mButtonPanel->setMouseOpaque(false); } else { mButtonPanel->setVisible(true); mButtonPanel->setMouseOpaque(true); } // Update enable/disable state and highlight state for editable toolbars if (!mReadOnly) { for (toolbar_button_list::iterator btn_it = mButtons.begin(); btn_it != mButtons.end(); ++btn_it) { LLToolBarButton* btn = *btn_it; LLCommand* command = LLCommandManager::instance().getCommand(btn->mId); if (command && btn->mIsEnabledSignal) { const bool button_command_enabled = (*btn->mIsEnabledSignal)(btn, command->isEnabledParameters()); btn->setEnabled(button_command_enabled); } if (command && btn->mIsRunningSignal) { const bool button_command_running = (*btn->mIsRunningSignal)(btn, command->isRunningParameters()); btn->setToggleState(button_command_running); } } } updateLayoutAsNeeded(); // rect may have shifted during layout LLUI::popMatrix(); LLUI::pushMatrix(); LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); // Position the caret if (!mCaretIcon) { mCaretIcon = getChild("caret"); } LLIconCtrl* caret = mCaretIcon; caret->setVisible(false); if (mDragAndDropTarget && !mButtonCommands.empty()) { LLRect caret_rect = caret->getRect(); if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) { caret->setRect(LLRect(mDragx-caret_rect.getWidth()/2+1, mDragy, mDragx+caret_rect.getWidth()/2+1, mDragy-mDragGirth)); } else { caret->setRect(LLRect(mDragx, mDragy+caret_rect.getHeight()/2, mDragx+mDragGirth, mDragy-caret_rect.getHeight()/2)); } caret->setVisible(true); } LLUICtrl::draw(); caret->setVisible(false); mDragAndDropTarget = false; } void LLToolBar::reshape(S32 width, S32 height, bool called_from_parent) { LLUICtrl::reshape(width, height, called_from_parent); mNeedsLayout = true; } void LLToolBar::createButtons() { std::set set_flashing; for (LLToolBarButton* button : mButtons) { if (button->getFlashTimer() && button->getFlashTimer()->isFlashingInProgress()) { set_flashing.insert(button->getCommandId().uuid()); } if (mButtonRemoveSignal) { (*mButtonRemoveSignal)(button); } delete button; } mButtons.clear(); mButtonMap.clear(); mRightMouseTargetButton = NULL; for (const LLCommandId& command_id : mButtonCommands) { LLToolBarButton* button = createButton(command_id); mButtons.push_back(button); mButtonPanel->addChild(button); mButtonMap.insert(std::make_pair(command_id.uuid(), button)); if (mButtonAddSignal) { (*mButtonAddSignal)(button); } if (set_flashing.find(button->getCommandId().uuid()) != set_flashing.end()) { button->setFlashing(true); } } mNeedsLayout = true; } void LLToolBarButton::callIfEnabled(LLUICtrl::commit_callback_t commit, LLUICtrl* ctrl, const LLSD& param ) { LLCommand* command = LLCommandManager::instance().getCommand(mId); if (!mIsEnabledSignal || (*mIsEnabledSignal)(this, command->isEnabledParameters())) { commit(ctrl, param); } } LLToolBarButton* LLToolBar::createButton(const LLCommandId& id) { LLCommand* commandp = LLCommandManager::instance().getCommand(id); if (!commandp) return NULL; LLToolBarButton::Params button_p; button_p.name = commandp->name(); button_p.label = LLTrans::getString(commandp->labelRef()); button_p.tool_tip = LLTrans::getString(commandp->tooltipRef()); button_p.image_overlay = LLUI::getUIImage(commandp->icon()); button_p.button_flash_enable = commandp->isFlashingAllowed(); button_p.overwriteFrom(mButtonParams[mButtonType]); LLToolBarButton* button = LLUICtrlFactory::create(button_p); if (!mReadOnly) { enable_callback_t isEnabledCB; const std::string& isEnabledFunction = commandp->isEnabledFunctionName(); if (isEnabledFunction.length() > 0) { LLUICtrl::EnableCallbackParam isEnabledParam; isEnabledParam.function_name = isEnabledFunction; isEnabledParam.parameter = commandp->isEnabledParameters(); isEnabledCB = initEnableCallback(isEnabledParam); if (NULL == button->mIsEnabledSignal) { button->mIsEnabledSignal = new enable_signal_t(); } button->mIsEnabledSignal->connect(isEnabledCB); } LLUICtrl::CommitCallbackParam executeParam; executeParam.function_name = commandp->executeFunctionName(); executeParam.parameter = commandp->executeParameters(); // If we have a "stop" function then we map the command to mouse down / mouse up otherwise commit const std::string& executeStopFunction = commandp->executeStopFunctionName(); if (executeStopFunction.length() > 0) { LLUICtrl::CommitCallbackParam executeStopParam; executeStopParam.function_name = executeStopFunction; executeStopParam.parameter = commandp->executeStopParameters(); LLUICtrl::commit_callback_t execute_func = initCommitCallback(executeParam); button->setFunctionName(commandp->executeFunctionName()); LL_DEBUGS("UIUsage") << "button function name a -> " << commandp->executeFunctionName() << LL_ENDL; LLUICtrl::commit_callback_t stop_func = initCommitCallback(executeStopParam); button->setMouseDownCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, execute_func, _1, _2)); button->setMouseUpCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, stop_func, _1, _2)); } else { button->setFunctionName(commandp->executeFunctionName()); LL_DEBUGS("UIUsage") << "button function name b -> " << commandp->executeFunctionName() << LL_ENDL; button->setCommitCallback(executeParam); } // Set up "is running" query callback const std::string& isRunningFunction = commandp->isRunningFunctionName(); if (isRunningFunction.length() > 0) { LLUICtrl::EnableCallbackParam isRunningParam; isRunningParam.function_name = isRunningFunction; isRunningParam.parameter = commandp->isRunningParameters(); enable_signal_t::slot_type isRunningCB = initEnableCallback(isRunningParam); if (NULL == button->mIsRunningSignal) { button->mIsRunningSignal = new enable_signal_t(); } button->mIsRunningSignal->connect(isRunningCB); } } // Drag and drop behavior must work also if provided in the Toybox and, potentially, any read-only toolbar button->setStartDragCallback(mStartDragItemCallback); button->setHandleDragCallback(mHandleDragItemCallback); button->setCommandId(id); return button; } boost::signals2::connection connectSignal(LLToolBar::button_signal_t*& signal, const LLToolBar::button_signal_t::slot_type& cb) { if (!signal) { signal = new LLToolBar::button_signal_t(); } return signal->connect(cb); } boost::signals2::connection LLToolBar::setButtonAddCallback(const button_signal_t::slot_type& cb) { return connectSignal(mButtonAddSignal, cb); } boost::signals2::connection LLToolBar::setButtonEnterCallback(const button_signal_t::slot_type& cb) { return connectSignal(mButtonEnterSignal, cb); } boost::signals2::connection LLToolBar::setButtonLeaveCallback(const button_signal_t::slot_type& cb) { return connectSignal(mButtonLeaveSignal, cb); } boost::signals2::connection LLToolBar::setButtonRemoveCallback(const button_signal_t::slot_type& cb) { return connectSignal(mButtonRemoveSignal, cb); } bool LLToolBar::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { // If we have a drop callback, that means that we can handle the drop bool handled = mHandleDropCallback != nullptr; // if drop is set, it's time to call the callback to get the operation done if (handled && drop) { handled = mHandleDropCallback(cargo_data, x, y, this); } // We accept only single tool drop on toolbars *accept = handled ? ACCEPT_YES_SINGLE : ACCEPT_NO; // We'll use that flag to change the visual aspect of the toolbar target on draw() mDragAndDropTarget = false; // Convert drag position into insert position and rank if (!isReadOnly() && handled && !drop) { if (cargo_type == DAD_WIDGET) { LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; LLCommandId dragged_command(inv_item->getUUID()); int orig_rank = getRankFromPosition(dragged_command); mDragRank = getRankFromPosition(x, y); // Don't DaD if we're dragging a command on itself mDragAndDropTarget = ((orig_rank != RANK_NONE) && ((mDragRank == orig_rank) || ((mDragRank - 1) == orig_rank))); //LL_INFOS() << "Merov debug : DaD, rank = " << mDragRank << ", dragged uui = " << inv_item->getUUID() << LL_ENDL; /* Do the following if you want to animate the button itself LLCommandId dragged_command(inv_item->getUUID()); removeCommand(dragged_command); addCommand(dragged_command,rank); */ } else { handled = false; } } return handled; } LLToolBarButton::LLToolBarButton(const Params& p) : LLButton(p), mMouseDownX(0), mMouseDownY(0), mWidthRange(p.button_width), mDesiredHeight(p.desired_height), mId(""), mIsEnabledSignal(NULL), mIsRunningSignal(NULL), mIsStartingSignal(NULL), mIsDragged(false), mStartDragItemCallback(NULL), mHandleDragItemCallback(NULL), mOriginalImageSelected(p.image_selected), mOriginalImageUnselected(p.image_unselected), mOriginalImagePressed(p.image_pressed), mOriginalImagePressedSelected(p.image_pressed_selected), mOriginalLabelColor(p.label_color), mOriginalLabelColorSelected(p.label_color_selected), mOriginalImageOverlayColor(p.image_overlay_color), mOriginalImageOverlaySelectedColor(p.image_overlay_selected_color) { } LLToolBarButton::~LLToolBarButton() { delete mIsEnabledSignal; delete mIsRunningSignal; delete mIsStartingSignal; } bool LLToolBarButton::handleMouseDown(S32 x, S32 y, MASK mask) { mMouseDownX = x; mMouseDownY = y; return LLButton::handleMouseDown(x, y, mask); } bool LLToolBarButton::handleHover(S32 x, S32 y, MASK mask) { bool handled = false; S32 mouse_distance_squared = (x - mMouseDownX) * (x - mMouseDownX) + (y - mMouseDownY) * (y - mMouseDownY); if (mouse_distance_squared > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD && hasMouseCapture() && mStartDragItemCallback && mHandleDragItemCallback) { if (!mIsDragged) { mStartDragItemCallback(x, y, this); mIsDragged = true; handled = true; } else { handled = mHandleDragItemCallback(x, y, mId.uuid(), LLAssetType::AT_WIDGET); } } else { handled = LLButton::handleHover(x, y, mask); } return handled; } void LLToolBarButton::onMouseEnter(S32 x, S32 y, MASK mask) { LLUICtrl::onMouseEnter(x, y, mask); // Always highlight toolbar buttons, even if they are disabled if (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this) { mNeedsHighlight = true; } LLToolBar* parent_toolbar = getParentByType(); if (parent_toolbar && parent_toolbar->mButtonEnterSignal) { (*(parent_toolbar->mButtonEnterSignal))(this); } } void LLToolBarButton::onMouseLeave(S32 x, S32 y, MASK mask) { LLButton::onMouseLeave(x, y, mask); LLToolBar* parent_toolbar = getParentByType(); if (parent_toolbar && parent_toolbar->mButtonLeaveSignal) { (*(parent_toolbar->mButtonLeaveSignal))(this); } } void LLToolBarButton::onMouseCaptureLost() { mIsDragged = false; } void LLToolBarButton::onCommit() { LLCommand* command = LLCommandManager::instance().getCommand(mId); if (!mIsEnabledSignal || (*mIsEnabledSignal)(this, command->isEnabledParameters())) { LLButton::onCommit(); } } void LLToolBarButton::reshape(S32 width, S32 height, bool called_from_parent) { LLButton::reshape(mWidthRange.clamp(width), height, called_from_parent); } void LLToolBarButton::setEnabled(bool enabled) { if (enabled) { mImageSelected = mOriginalImageSelected; mImageUnselected = mOriginalImageUnselected; mImagePressed = mOriginalImagePressed; mImagePressedSelected = mOriginalImagePressedSelected; mUnselectedLabelColor = mOriginalLabelColor; mSelectedLabelColor = mOriginalLabelColorSelected; mImageOverlayColor = mOriginalImageOverlayColor; mImageOverlaySelectedColor = mOriginalImageOverlaySelectedColor; } else { mImageSelected = mImageDisabledSelected; mImageUnselected = mImageDisabled; mImagePressed = mImageDisabled; mImagePressedSelected = mImageDisabledSelected; mUnselectedLabelColor = mDisabledLabelColor; mSelectedLabelColor = mDisabledSelectedLabelColor; mImageOverlayColor = mImageOverlayDisabledColor; mImageOverlaySelectedColor = mImageOverlayDisabledColor; } } const std::string LLToolBarButton::getToolTip() const { std::string tooltip; if (labelIsTruncated() || getCurrentLabel().empty()) { tooltip = LLTrans::getString(LLCommandManager::instance().getCommand(mId)->labelRef()) + " -- " + LLView::getToolTip(); } else { tooltip = LLView::getToolTip(); } LLToolBar* parent_toolbar = getParentByType(); if (parent_toolbar && parent_toolbar->mButtonTooltipSuffix.length() > 0) { tooltip = tooltip + "\n(" + parent_toolbar->mButtonTooltipSuffix + ")"; } 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); } }