/** * @file llhints.cpp * @brief Hint popups for displaying context sensitive help in a UI overlay * * $LicenseInfo:firstyear=2000&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 "llviewerprecompiledheaders.h" // must be first include #include "llhints.h" #include "llbutton.h" #include "lltextbox.h" #include "llviewerwindow.h" #include "llviewercontrol.h" #include "lliconctrl.h" #include "llsdparam.h" class LLHintPopup : public LLPanel { public: typedef enum e_popup_direction { LEFT, TOP, RIGHT, BOTTOM, TOP_RIGHT } EPopupDirection; struct PopupDirections : public LLInitParam::TypeValuesHelper { static void declareValues() { declare("left", LLHintPopup::LEFT); declare("right", LLHintPopup::RIGHT); declare("top", LLHintPopup::TOP); declare("bottom", LLHintPopup::BOTTOM); declare("top_right", LLHintPopup::TOP_RIGHT); } }; struct TargetParams : public LLInitParam::Block { Mandatory target; Mandatory direction; TargetParams() : target("target"), direction("direction") {} }; struct Params : public LLInitParam::Block { Mandatory notification; Optional target_params; Optional distance; Optional left_arrow, up_arrow, right_arrow, down_arrow, lower_left_arrow, hint_image; Optional left_arrow_offset, up_arrow_offset, right_arrow_offset, down_arrow_offset; Optional fade_in_time, fade_out_time; Params() : distance("distance"), left_arrow("left_arrow"), up_arrow("up_arrow"), right_arrow("right_arrow"), down_arrow("down_arrow"), lower_left_arrow("lower_left_arrow"), hint_image("hint_image"), left_arrow_offset("left_arrow_offset"), up_arrow_offset("up_arrow_offset"), right_arrow_offset("right_arrow_offset"), down_arrow_offset("down_arrow_offset"), fade_in_time("fade_in_time"), fade_out_time("fade_out_time") {} }; LLHintPopup(const Params&); /*virtual*/ bool postBuild(); void onClickClose() { if (!mHidden) { hide(); LLNotifications::instance().cancel(mNotification); } } void draw(); void hide() { if(!mHidden) {mHidden = true; mFadeTimer.reset();} } private: LLNotificationPtr mNotification; std::string mTarget; EPopupDirection mDirection; S32 mDistance; LLUIImagePtr mArrowLeft, mArrowUp, mArrowRight, mArrowDown, mArrowDownAndLeft; S32 mArrowLeftOffset, mArrowUpOffset, mArrowRightOffset, mArrowDownOffset; LLFrameTimer mFadeTimer; F32 mFadeInTime, mFadeOutTime; bool mHidden; }; static LLDefaultChildRegistry::Register r("hint_popup"); LLHintPopup::LLHintPopup(const LLHintPopup::Params& p) : mNotification(p.notification), mDirection(TOP), mDistance(p.distance), mArrowLeft(p.left_arrow), mArrowUp(p.up_arrow), mArrowRight(p.right_arrow), mArrowDown(p.down_arrow), mArrowDownAndLeft(p.lower_left_arrow), mArrowLeftOffset(p.left_arrow_offset), mArrowUpOffset(p.up_arrow_offset), mArrowRightOffset(p.right_arrow_offset), mArrowDownOffset(p.down_arrow_offset), mHidden(false), mFadeInTime(p.fade_in_time), mFadeOutTime(p.fade_out_time), LLPanel(p) { if (p.target_params.isProvided()) { mDirection = p.target_params.direction; mTarget = p.target_params.target; } if (p.hint_image.isProvided()) { buildFromFile("panel_hint_image.xml", p); getChild("hint_image")->setImage(p.hint_image()); } else { buildFromFile( "panel_hint.xml", p); } } bool LLHintPopup::postBuild() { LLTextBox& hint_text = getChildRef("hint_text"); hint_text.setText(mNotification->getMessage()); getChild("close")->setClickedCallback(boost::bind(&LLHintPopup::onClickClose, this)); getChild("hint_title")->setText(mNotification->getLabel()); LLRect text_bounds = hint_text.getTextBoundingRect(); S32 delta_height = text_bounds.getHeight() - hint_text.getRect().getHeight(); reshape(getRect().getWidth(), getRect().getHeight() + delta_height); hint_text.reshape(hint_text.getRect().getWidth(), hint_text.getRect().getHeight() + delta_height); // hint_text.translate(0, -delta_height); return true; } void LLHintPopup::draw() { F32 alpha = 1.f; if (mHidden) { alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, mFadeOutTime, 1.f, 0.f); if (alpha == 0.f) { die(); return; } } else { alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, mFadeInTime, 0.f, 1.f); } LLIconCtrl* hint_icon = findChild("hint_image"); if (hint_icon) { LLUIImagePtr hint_image = hint_icon->getImage(); S32 image_height = hint_image.isNull() ? 0 : hint_image->getHeight(); S32 image_width = hint_image.isNull() ? 0 : hint_image->getWidth(); LLView* layout_stack = hint_icon->getParent()->getParent(); S32 delta_height = image_height - layout_stack->getRect().getHeight(); hint_icon->getParent()->reshape(image_width, hint_icon->getParent()->getRect().getHeight()); layout_stack->reshape(layout_stack->getRect().getWidth(), image_height); layout_stack->translate(0, -delta_height); LLRect hint_rect = getLocalRect(); reshape(hint_rect.getWidth(), hint_rect.getHeight() + delta_height); } { LLViewDrawContext context(alpha); if (mTarget.empty()) { // just draw contents, no arrow, in default position LLPanel::draw(); } else { LLView* targetp = LLHints::getInstance()->getHintTarget(mTarget).get(); if (!targetp) { // target widget is no longer valid, go away die(); } else if (!targetp->isInVisibleChain()) { // if target is invisible, don't draw, but keep alive in case widget comes back // but do make it so that it allows mouse events to pass through setEnabled(false); setMouseOpaque(false); } else { // revert back enabled and mouse opaque state in case we disabled it before setEnabled(true); setMouseOpaque(true); LLRect target_rect; targetp->localRectToOtherView(targetp->getLocalRect(), &target_rect, getParent()); LLRect my_local_rect = getLocalRect(); LLRect my_rect; LLRect arrow_rect; LLUIImagePtr arrow_imagep; switch(mDirection) { case LEFT: my_rect.setCenterAndSize( target_rect.mLeft - (my_local_rect.getWidth() / 2 + mDistance), target_rect.getCenterY(), my_local_rect.getWidth(), my_local_rect.getHeight()); if (mArrowRight) { arrow_rect.setCenterAndSize(my_local_rect.mRight + mArrowRight->getWidth() / 2 + mArrowRightOffset, my_local_rect.getCenterY(), mArrowRight->getWidth(), mArrowRight->getHeight()); arrow_imagep = mArrowRight; } break; case TOP: my_rect.setCenterAndSize( target_rect.getCenterX(), target_rect.mTop + (my_local_rect.getHeight() / 2 + mDistance), my_local_rect.getWidth(), my_local_rect.getHeight()); if (mArrowDown) { arrow_rect.setCenterAndSize(my_local_rect.getCenterX(), my_local_rect.mBottom - mArrowDown->getHeight() / 2 + mArrowDownOffset, mArrowDown->getWidth(), mArrowDown->getHeight()); arrow_imagep = mArrowDown; } break; case RIGHT: my_rect.setCenterAndSize( target_rect.mRight + (my_local_rect.getWidth() / 2 + mDistance), target_rect.getCenterY(), my_local_rect.getWidth(), my_local_rect.getHeight()); if (mArrowLeft) { arrow_rect.setCenterAndSize(my_local_rect.mLeft - mArrowLeft->getWidth() / 2 + mArrowLeftOffset, my_local_rect.getCenterY(), mArrowLeft->getWidth(), mArrowLeft->getHeight()); arrow_imagep = mArrowLeft; } break; case BOTTOM: my_rect.setCenterAndSize( target_rect.getCenterX(), target_rect.mBottom - (my_local_rect.getHeight() / 2 + mDistance), my_local_rect.getWidth(), my_local_rect.getHeight()); if (mArrowUp) { arrow_rect.setCenterAndSize(my_local_rect.getCenterX(), my_local_rect.mTop + mArrowUp->getHeight() / 2 + mArrowUpOffset, mArrowUp->getWidth(), mArrowUp->getHeight()); arrow_imagep = mArrowUp; } break; case TOP_RIGHT: my_rect.setCenterAndSize( target_rect.mRight + (my_local_rect.getWidth() / 2), target_rect.mTop + (my_local_rect.getHeight() / 2 + mDistance), my_local_rect.getWidth(), my_local_rect.getHeight()); if (mArrowDownAndLeft) { arrow_rect.setCenterAndSize(my_local_rect.mLeft + mArrowDownAndLeft->getWidth() / 2 + mArrowLeftOffset, my_local_rect.mBottom - mArrowDownAndLeft->getHeight() / 2 + mArrowDownOffset, mArrowDownAndLeft->getWidth(), mArrowDownAndLeft->getHeight()); arrow_imagep = mArrowDownAndLeft; } } setShape(my_rect); LLPanel::draw(); if (arrow_imagep) arrow_imagep->draw(arrow_rect, LLColor4(1.f, 1.f, 1.f, alpha)); } } } } /// LLHints LLHints::LLHints() { LLControlVariablePtr control = gSavedSettings.getControl("EnableUIHints"); mControlConnection = control->getSignal()->connect(boost::bind(&LLHints::showHints, this, _2)); gViewerWindow->getHintHolder()->setVisible(control->getValue().asBoolean()); } LLHints::~LLHints() { mControlConnection.disconnect(); } void LLHints::show(LLNotificationPtr hint) { LLHintPopup::Params p(LLUICtrlFactory::getDefaultParams()); LLParamSDParser parser; parser.readSD(hint->getPayload(), p, true); p.notification = hint; if (p.validateBlock()) { LLHintPopup* popup = new LLHintPopup(p); mHints[hint] = popup; LLView* hint_holder = gViewerWindow->getHintHolder(); if (hint_holder) { hint_holder->addChild(popup); popup->centerWithin(hint_holder->getLocalRect()); } } } void LLHints::hide(LLNotificationPtr hint) { hint_map_t::iterator found_it = mHints.find(hint); if (found_it != mHints.end()) { found_it->second->hide(); mHints.erase(found_it); } } void LLHints::registerHintTarget(const std::string& name, LLHandle target) { mTargetRegistry.defaultRegistrar().replace(name, target); } LLHandle LLHints::getHintTarget(const std::string& name) { LLHandle* handlep = mTargetRegistry.getValue(name); if (handlep) { return *handlep; } else { return LLHandle(); } } void LLHints::showHints(const LLSD& show) { bool visible = show.asBoolean(); gViewerWindow->getHintHolder()->setVisible(visible); }