1 #include "tooltips.hpp" 2 3 #include <iomanip> 4 5 #include <MyGUI_Gui.h> 6 #include <MyGUI_RenderManager.h> 7 #include <MyGUI_InputManager.h> 8 #include <MyGUI_ImageBox.h> 9 10 #include <components/settings/settings.hpp> 11 #include <components/widgets/box.hpp> 12 13 #include "../mwbase/world.hpp" 14 #include "../mwbase/environment.hpp" 15 #include "../mwbase/windowmanager.hpp" 16 #include "../mwbase/mechanicsmanager.hpp" 17 18 #include "../mwworld/class.hpp" 19 #include "../mwworld/esmstore.hpp" 20 #include "../mwmechanics/spellutil.hpp" 21 #include "../mwmechanics/actorutil.hpp" 22 23 #include "mapwindow.hpp" 24 #include "inventorywindow.hpp" 25 26 #include "itemmodel.hpp" 27 28 namespace MWGui 29 { 30 std::string ToolTips::sSchoolNames[] = {"#{sSchoolAlteration}", "#{sSchoolConjuration}", "#{sSchoolDestruction}", "#{sSchoolIllusion}", "#{sSchoolMysticism}", "#{sSchoolRestoration}"}; 31 ToolTips()32 ToolTips::ToolTips() : 33 Layout("openmw_tooltips.layout") 34 , mFocusToolTipX(0.0) 35 , mFocusToolTipY(0.0) 36 , mHorizontalScrollIndex(0) 37 , mDelay(0.0) 38 , mRemainingDelay(0.0) 39 , mLastMouseX(0) 40 , mLastMouseY(0) 41 , mEnabled(true) 42 , mFullHelp(false) 43 , mShowOwned(0) 44 , mFrameDuration(0.f) 45 { 46 getWidget(mDynamicToolTipBox, "DynamicToolTipBox"); 47 48 mDynamicToolTipBox->setVisible(false); 49 50 // turn off mouse focus so that getMouseFocusWidget returns the correct widget, 51 // even if the mouse is over the tooltip 52 mDynamicToolTipBox->setNeedMouseFocus(false); 53 mMainWidget->setNeedMouseFocus(false); 54 55 mDelay = Settings::Manager::getFloat("tooltip delay", "GUI"); 56 mRemainingDelay = mDelay; 57 58 for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) 59 { 60 mMainWidget->getChildAt(i)->setVisible(false); 61 } 62 63 mShowOwned = Settings::Manager::getInt("show owned", "Game"); 64 } 65 setEnabled(bool enabled)66 void ToolTips::setEnabled(bool enabled) 67 { 68 mEnabled = enabled; 69 } 70 onFrame(float frameDuration)71 void ToolTips::onFrame(float frameDuration) 72 { 73 mFrameDuration = frameDuration; 74 } 75 update(float frameDuration)76 void ToolTips::update(float frameDuration) 77 { 78 79 while (mDynamicToolTipBox->getChildCount()) 80 { 81 MyGUI::Gui::getInstance().destroyWidget(mDynamicToolTipBox->getChildAt(0)); 82 } 83 84 // start by hiding everything 85 for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) 86 { 87 mMainWidget->getChildAt(i)->setVisible(false); 88 } 89 90 const MyGUI::IntSize &viewSize = MyGUI::RenderManager::getInstance().getViewSize(); 91 92 if (!mEnabled) 93 { 94 return; 95 } 96 97 MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); 98 bool guiMode = winMgr->isGuiMode(); 99 100 if (guiMode) 101 { 102 if (!winMgr->getCursorVisible()) 103 return; 104 const MyGUI::IntPoint& mousePos = MyGUI::InputManager::getInstance().getMousePosition(); 105 106 if (winMgr->getWorldMouseOver() && 107 (winMgr->isConsoleMode() || 108 (winMgr->getMode() == GM_Container) || 109 (winMgr->getMode() == GM_Inventory))) 110 { 111 if (mFocusObject.isEmpty ()) 112 return; 113 114 const MWWorld::Class& objectclass = mFocusObject.getClass(); 115 116 MyGUI::IntSize tooltipSize; 117 if (!objectclass.hasToolTip(mFocusObject) && winMgr->isConsoleMode()) 118 { 119 setCoord(0, 0, 300, 300); 120 mDynamicToolTipBox->setVisible(true); 121 ToolTipInfo info; 122 info.caption = mFocusObject.getClass().getName(mFocusObject); 123 if (info.caption.empty()) 124 info.caption=mFocusObject.getCellRef().getRefId(); 125 info.icon=""; 126 tooltipSize = createToolTip(info, checkOwned()); 127 } 128 else 129 tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true); 130 131 MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); 132 position(tooltipPosition, tooltipSize, viewSize); 133 134 setCoord(tooltipPosition.left, tooltipPosition.top, tooltipSize.width, tooltipSize.height); 135 } 136 137 else 138 { 139 if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY) 140 { 141 mRemainingDelay -= frameDuration; 142 } 143 else 144 { 145 mHorizontalScrollIndex = 0; 146 mRemainingDelay = mDelay; 147 } 148 mLastMouseX = mousePos.left; 149 mLastMouseY = mousePos.top; 150 151 152 if (mRemainingDelay > 0) 153 return; 154 155 MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget(); 156 if (focus == nullptr) 157 return; 158 159 MyGUI::IntSize tooltipSize; 160 161 // try to go 1 level up until there is a widget that has tooltip 162 // this is necessary because some skin elements are actually separate widgets 163 int i=0; 164 while (!focus->isUserString("ToolTipType")) 165 { 166 focus = focus->getParent(); 167 if (!focus) 168 return; 169 ++i; 170 } 171 172 std::string type = focus->getUserString("ToolTipType"); 173 174 if (type == "") 175 { 176 return; 177 } 178 179 180 // special handling for markers on the local map: the tooltip should only be visible 181 // if the marker is not hidden due to the fog of war. 182 if (type == "MapMarker") 183 { 184 LocalMapBase::MarkerUserData data = *focus->getUserData<LocalMapBase::MarkerUserData>(); 185 186 if (!data.isPositionExplored()) 187 return; 188 189 ToolTipInfo info; 190 info.text = data.caption; 191 info.notes = data.notes; 192 tooltipSize = createToolTip(info); 193 } 194 else if (type == "ItemPtr") 195 { 196 mFocusObject = *focus->getUserData<MWWorld::Ptr>(); 197 if (!mFocusObject) 198 return; 199 200 tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned()); 201 } 202 else if (type == "ItemModelIndex") 203 { 204 std::pair<ItemModel::ModelIndex, ItemModel*> pair = *focus->getUserData<std::pair<ItemModel::ModelIndex, ItemModel*> >(); 205 mFocusObject = pair.second->getItem(pair.first).mBase; 206 bool isAllowedToUse = pair.second->allowedToUseItems(); 207 tooltipSize = getToolTipViaPtr(pair.second->getItem(pair.first).mCount, false, !isAllowedToUse); 208 } 209 else if (type == "ToolTipInfo") 210 { 211 tooltipSize = createToolTip(*focus->getUserData<MWGui::ToolTipInfo>()); 212 } 213 else if (type == "AvatarItemSelection") 214 { 215 MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord(); 216 MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top); 217 MWWorld::Ptr item = winMgr->getInventoryWindow ()->getAvatarSelectedItem (relMousePos.left, relMousePos.top); 218 219 mFocusObject = item; 220 if (!mFocusObject.isEmpty ()) 221 tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); 222 } 223 else if (type == "Spell") 224 { 225 ToolTipInfo info; 226 227 const ESM::Spell *spell = 228 MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(focus->getUserString("Spell")); 229 info.caption = spell->mName; 230 Widgets::SpellEffectList effects; 231 for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList) 232 { 233 Widgets::SpellEffectParams params; 234 params.mEffectID = spellEffect.mEffectID; 235 params.mSkill = spellEffect.mSkill; 236 params.mAttribute = spellEffect.mAttribute; 237 params.mDuration = spellEffect.mDuration; 238 params.mMagnMin = spellEffect.mMagnMin; 239 params.mMagnMax = spellEffect.mMagnMax; 240 params.mRange = spellEffect.mRange; 241 params.mArea = spellEffect.mArea; 242 params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability); 243 params.mNoTarget = false; 244 effects.push_back(params); 245 } 246 if (MWMechanics::spellIncreasesSkill(spell)) // display school of spells that contribute to skill progress 247 { 248 MWWorld::Ptr player = MWMechanics::getPlayer(); 249 int school = MWMechanics::getSpellSchool(spell, player); 250 info.text = "#{sSchool}: " + sSchoolNames[school]; 251 } 252 std::string cost = focus->getUserString("SpellCost"); 253 if (cost != "" && cost != "0") 254 info.text += MWGui::ToolTips::getValueString(spell->mData.mCost, "#{sCastCost}"); 255 info.effects = effects; 256 tooltipSize = createToolTip(info); 257 } 258 else if (type == "Layout") 259 { 260 // tooltip defined in the layout 261 MyGUI::Widget* tooltip; 262 getWidget(tooltip, focus->getUserString("ToolTipLayout")); 263 264 tooltip->setVisible(true); 265 266 std::map<std::string, std::string> userStrings = focus->getUserStrings(); 267 for (auto& userStringPair : userStrings) 268 { 269 size_t underscorePos = userStringPair.first.find('_'); 270 if (underscorePos == std::string::npos) 271 continue; 272 std::string key = userStringPair.first.substr(0, underscorePos); 273 std::string widgetName = userStringPair.first.substr(underscorePos+1, userStringPair.first.size()-(underscorePos+1)); 274 275 type = "Property"; 276 size_t caretPos = key.find('^'); 277 if (caretPos != std::string::npos) 278 { 279 type = key.substr(0, caretPos); 280 key.erase(key.begin(), key.begin() + caretPos + 1); 281 } 282 283 MyGUI::Widget* w; 284 getWidget(w, widgetName); 285 if (type == "Property") 286 w->setProperty(key, userStringPair.second); 287 else if (type == "UserData") 288 w->setUserString(key, userStringPair.second); 289 } 290 291 tooltipSize = tooltip->getSize(); 292 293 tooltip->setCoord(0, 0, tooltipSize.width, tooltipSize.height); 294 } 295 else 296 throw std::runtime_error ("unknown tooltip type"); 297 298 MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); 299 300 position(tooltipPosition, tooltipSize, viewSize); 301 302 setCoord(tooltipPosition.left, tooltipPosition.top, tooltipSize.width, tooltipSize.height); 303 } 304 } 305 else 306 { 307 if (!mFocusObject.isEmpty()) 308 { 309 MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true, checkOwned()); 310 311 setCoord(viewSize.width/2 - tooltipSize.width/2, 312 std::max(0, int(mFocusToolTipY*viewSize.height - tooltipSize.height)), 313 tooltipSize.width, 314 tooltipSize.height); 315 316 mDynamicToolTipBox->setVisible(true); 317 } 318 } 319 } 320 position(MyGUI::IntPoint & position,MyGUI::IntSize size,MyGUI::IntSize viewportSize)321 void ToolTips::position(MyGUI::IntPoint& position, MyGUI::IntSize size, MyGUI::IntSize viewportSize) 322 { 323 position += MyGUI::IntPoint(0, 32) 324 - MyGUI::IntPoint(static_cast<int>(MyGUI::InputManager::getInstance().getMousePosition().left / float(viewportSize.width) * size.width), 0); 325 326 if ((position.left + size.width) > viewportSize.width) 327 { 328 position.left = viewportSize.width - size.width; 329 } 330 if ((position.top + size.height) > viewportSize.height) 331 { 332 position.top = MyGUI::InputManager::getInstance().getMousePosition().top - size.height - 8; 333 } 334 } 335 clear()336 void ToolTips::clear() 337 { 338 mFocusObject = MWWorld::Ptr(); 339 340 while (mDynamicToolTipBox->getChildCount()) 341 { 342 MyGUI::Gui::getInstance().destroyWidget(mDynamicToolTipBox->getChildAt(0)); 343 } 344 345 for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) 346 { 347 mMainWidget->getChildAt(i)->setVisible(false); 348 } 349 } 350 setFocusObject(const MWWorld::Ptr & focus)351 void ToolTips::setFocusObject(const MWWorld::Ptr& focus) 352 { 353 mFocusObject = focus; 354 355 update(mFrameDuration); 356 } 357 getToolTipViaPtr(int count,bool image,bool isOwned)358 MyGUI::IntSize ToolTips::getToolTipViaPtr (int count, bool image, bool isOwned) 359 { 360 // this the maximum width of the tooltip before it starts word-wrapping 361 setCoord(0, 0, 300, 300); 362 363 MyGUI::IntSize tooltipSize; 364 365 const MWWorld::Class& object = mFocusObject.getClass(); 366 if (!object.hasToolTip(mFocusObject)) 367 { 368 mDynamicToolTipBox->setVisible(false); 369 } 370 else 371 { 372 mDynamicToolTipBox->setVisible(true); 373 374 ToolTipInfo info = object.getToolTipInfo(mFocusObject, count); 375 if (!image) 376 info.icon = ""; 377 tooltipSize = createToolTip(info, isOwned); 378 } 379 380 return tooltipSize; 381 } 382 checkOwned()383 bool ToolTips::checkOwned() 384 { 385 if(mFocusObject.isEmpty()) 386 return false; 387 388 MWWorld::Ptr ptr = MWMechanics::getPlayer(); 389 MWWorld::Ptr victim; 390 391 MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); 392 return !mm->isAllowedToUse(ptr, mFocusObject, victim); 393 } 394 createToolTip(const MWGui::ToolTipInfo & info,bool isOwned)395 MyGUI::IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info, bool isOwned) 396 { 397 mDynamicToolTipBox->setVisible(true); 398 399 if((mShowOwned == 1 || mShowOwned == 3) && isOwned) 400 mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp_Owned" : "HUD_Box_Owned"); 401 else 402 mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp" : "HUD_Box"); 403 404 std::string caption = info.caption; 405 std::string image = info.icon; 406 int imageSize = (image != "") ? info.imageSize : 0; 407 std::string text = info.text; 408 409 // remove the first newline (easier this way) 410 if (text.size() > 0 && text[0] == '\n') 411 text.erase(0, 1); 412 413 const ESM::Enchantment* enchant = nullptr; 414 const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); 415 if (info.enchant != "") 416 { 417 enchant = store.get<ESM::Enchantment>().search(info.enchant); 418 if (enchant) 419 { 420 if (enchant->mData.mType == ESM::Enchantment::CastOnce) 421 text += "\n#{sItemCastOnce}"; 422 else if (enchant->mData.mType == ESM::Enchantment::WhenStrikes) 423 text += "\n#{sItemCastWhenStrikes}"; 424 else if (enchant->mData.mType == ESM::Enchantment::WhenUsed) 425 text += "\n#{sItemCastWhenUsed}"; 426 else if (enchant->mData.mType == ESM::Enchantment::ConstantEffect) 427 text += "\n#{sItemCastConstant}"; 428 } 429 } 430 431 // this the maximum width of the tooltip before it starts word-wrapping 432 setCoord(0, 0, 300, 300); 433 434 const MyGUI::IntPoint padding(8, 8); 435 436 const int imageCaptionHPadding = (caption != "" ? 8 : 0); 437 const int imageCaptionVPadding = (caption != "" ? 4 : 0); 438 439 const int maximumWidth = MyGUI::RenderManager::getInstance().getViewSize().width - imageCaptionHPadding * 2; 440 441 std::string realImage = MWBase::Environment::get().getWindowManager()->correctIconPath(image); 442 443 Gui::EditBox* captionWidget = mDynamicToolTipBox->createWidget<Gui::EditBox>("NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); 444 captionWidget->setEditStatic(true); 445 captionWidget->setNeedKeyFocus(false); 446 captionWidget->setCaptionWithReplacing(caption); 447 MyGUI::IntSize captionSize = captionWidget->getTextSize(); 448 449 int captionHeight = std::max(caption != "" ? captionSize.height : 0, imageSize); 450 451 Gui::EditBox* textWidget = mDynamicToolTipBox->createWidget<Gui::EditBox>("SandText", MyGUI::IntCoord(0, captionHeight+imageCaptionVPadding, 300, 300-captionHeight-imageCaptionVPadding), MyGUI::Align::Stretch, "ToolTipText"); 452 textWidget->setEditStatic(true); 453 textWidget->setEditMultiLine(true); 454 textWidget->setEditWordWrap(info.wordWrap); 455 textWidget->setCaptionWithReplacing(text); 456 textWidget->setTextAlign(MyGUI::Align::HCenter | MyGUI::Align::Top); 457 textWidget->setNeedKeyFocus(false); 458 MyGUI::IntSize textSize = textWidget->getTextSize(); 459 460 captionSize += MyGUI::IntSize(imageSize, 0); // adjust for image 461 MyGUI::IntSize totalSize = MyGUI::IntSize( std::min(std::max(textSize.width,captionSize.width + ((image != "") ? imageCaptionHPadding : 0)),maximumWidth), 462 ((text != "") ? textSize.height + imageCaptionVPadding : 0) + captionHeight ); 463 464 for (const std::string& note : info.notes) 465 { 466 MyGUI::ImageBox* icon = mDynamicToolTipBox->createWidget<MyGUI::ImageBox>("MarkerButton", 467 MyGUI::IntCoord(padding.left, totalSize.height+padding.top, 8, 8), MyGUI::Align::Default); 468 icon->setColour(MyGUI::Colour(1.0f, 0.3f, 0.3f)); 469 Gui::EditBox* edit = mDynamicToolTipBox->createWidget<Gui::EditBox>("SandText", 470 MyGUI::IntCoord(padding.left+8+4, totalSize.height+padding.top, 300-padding.left-8-4, 300-totalSize.height), 471 MyGUI::Align::Default); 472 edit->setEditMultiLine(true); 473 edit->setEditWordWrap(true); 474 edit->setCaption(note); 475 edit->setSize(edit->getWidth(), edit->getTextSize().height); 476 icon->setPosition(icon->getLeft(),(edit->getTop()+edit->getBottom())/2-icon->getHeight()/2); 477 totalSize.height += std::max(edit->getHeight(), icon->getHeight()); 478 totalSize.width = std::max(totalSize.width, edit->getWidth()+8+4); 479 } 480 481 if (!info.effects.empty()) 482 { 483 MyGUI::Widget* effectArea = mDynamicToolTipBox->createWidget<MyGUI::Widget>("", 484 MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), 485 MyGUI::Align::Stretch); 486 487 MyGUI::IntCoord coord(0, 6, totalSize.width, 24); 488 489 Widgets::MWEffectListPtr effectsWidget = effectArea->createWidget<Widgets::MWEffectList> 490 ("MW_StatName", coord, MyGUI::Align::Default); 491 effectsWidget->setEffectList(info.effects); 492 493 std::vector<MyGUI::Widget*> effectItems; 494 int flag = info.isPotion ? Widgets::MWEffectList::EF_NoTarget : 0; 495 flag |= info.isIngredient ? Widgets::MWEffectList::EF_NoMagnitude : 0; 496 effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, flag); 497 totalSize.height += coord.top-6; 498 totalSize.width = std::max(totalSize.width, coord.width); 499 } 500 501 if (enchant) 502 { 503 MyGUI::Widget* enchantArea = mDynamicToolTipBox->createWidget<MyGUI::Widget>("", 504 MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), 505 MyGUI::Align::Stretch); 506 507 MyGUI::IntCoord coord(0, 6, totalSize.width, 24); 508 509 Widgets::MWEffectListPtr enchantWidget = enchantArea->createWidget<Widgets::MWEffectList> 510 ("MW_StatName", coord, MyGUI::Align::Default); 511 enchantWidget->setEffectList(Widgets::MWEffectList::effectListFromESM(&enchant->mEffects)); 512 513 std::vector<MyGUI::Widget*> enchantEffectItems; 514 int flag = (enchant->mData.mType == ESM::Enchantment::ConstantEffect) ? Widgets::MWEffectList::EF_Constant : 0; 515 enchantWidget->createEffectWidgets(enchantEffectItems, enchantArea, coord, true, flag); 516 totalSize.height += coord.top-6; 517 totalSize.width = std::max(totalSize.width, coord.width); 518 519 if (enchant->mData.mType == ESM::Enchantment::WhenStrikes 520 || enchant->mData.mType == ESM::Enchantment::WhenUsed) 521 { 522 int maxCharge = enchant->mData.mCharge; 523 int charge = (info.remainingEnchantCharge == -1) ? maxCharge : info.remainingEnchantCharge; 524 525 const int chargeWidth = 204; 526 527 MyGUI::TextBox* chargeText = enchantArea->createWidget<MyGUI::TextBox>("SandText", MyGUI::IntCoord(0, 0, 10, 18), MyGUI::Align::Default, "ToolTipEnchantChargeText"); 528 chargeText->setCaptionWithReplacing("#{sCharges}"); 529 530 const int chargeTextWidth = chargeText->getTextSize().width + 5; 531 532 const int chargeAndTextWidth = chargeWidth + chargeTextWidth; 533 534 totalSize.width = std::max(totalSize.width, chargeAndTextWidth); 535 536 chargeText->setCoord((totalSize.width - chargeAndTextWidth)/2, coord.top+6, chargeTextWidth, 18); 537 538 MyGUI::IntCoord chargeCoord; 539 if (totalSize.width < chargeWidth) 540 { 541 totalSize.width = chargeWidth; 542 chargeCoord = MyGUI::IntCoord(0, coord.top+6, chargeWidth, 18); 543 } 544 else 545 { 546 chargeCoord = MyGUI::IntCoord((totalSize.width - chargeAndTextWidth)/2 + chargeTextWidth, coord.top+6, chargeWidth, 18); 547 } 548 Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget<Widgets::MWDynamicStat> 549 ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default); 550 chargeWidget->setValue(charge, maxCharge); 551 totalSize.height += 24; 552 } 553 } 554 555 captionWidget->setCoord( (totalSize.width - captionSize.width)/2 + imageSize, 556 (captionHeight-captionSize.height)/2, 557 captionSize.width-imageSize, 558 captionSize.height); 559 560 //if its too long we do hscroll with the caption 561 if (captionSize.width > maximumWidth) 562 { 563 mHorizontalScrollIndex = mHorizontalScrollIndex + 2; 564 if (mHorizontalScrollIndex > captionSize.width){ 565 mHorizontalScrollIndex = -totalSize.width; 566 } 567 int horizontal_scroll = mHorizontalScrollIndex; 568 if (horizontal_scroll < 40){ 569 horizontal_scroll = 40; 570 }else{ 571 horizontal_scroll = 80 - mHorizontalScrollIndex; 572 } 573 captionWidget->setPosition (MyGUI::IntPoint(horizontal_scroll, captionWidget->getPosition().top + padding.top)); 574 } else { 575 captionWidget->setPosition (captionWidget->getPosition() + padding); 576 } 577 578 textWidget->setPosition (textWidget->getPosition() + MyGUI::IntPoint(0, padding.top)); // only apply vertical padding, the horizontal works automatically due to Align::HCenter 579 580 if (image != "") 581 { 582 MyGUI::ImageBox* imageWidget = mDynamicToolTipBox->createWidget<MyGUI::ImageBox>("ImageBox", 583 MyGUI::IntCoord((totalSize.width - captionSize.width - imageCaptionHPadding)/2, 0, imageSize, imageSize), 584 MyGUI::Align::Left | MyGUI::Align::Top); 585 imageWidget->setImageTexture(realImage); 586 imageWidget->setPosition (imageWidget->getPosition() + padding); 587 } 588 589 totalSize += MyGUI::IntSize(padding.left*2, padding.top*2); 590 591 return totalSize; 592 } 593 toString(const float value)594 std::string ToolTips::toString(const float value) 595 { 596 std::ostringstream stream; 597 598 if (value != int(value)) 599 stream << std::setprecision(3); 600 601 stream << value; 602 return stream.str(); 603 } 604 toString(const int value)605 std::string ToolTips::toString(const int value) 606 { 607 return std::to_string(value); 608 } 609 getWeightString(const float weight,const std::string & prefix)610 std::string ToolTips::getWeightString(const float weight, const std::string& prefix) 611 { 612 if (weight == 0) 613 return ""; 614 else 615 return "\n" + prefix + ": " + toString(weight); 616 } 617 getPercentString(const float value,const std::string & prefix)618 std::string ToolTips::getPercentString(const float value, const std::string& prefix) 619 { 620 if (value == 0) 621 return ""; 622 else 623 return "\n" + prefix + ": " + toString(value*100) +"%"; 624 } 625 getValueString(const int value,const std::string & prefix)626 std::string ToolTips::getValueString(const int value, const std::string& prefix) 627 { 628 if (value == 0) 629 return ""; 630 else 631 return "\n" + prefix + ": " + toString(value); 632 } 633 getMiscString(const std::string & text,const std::string & prefix)634 std::string ToolTips::getMiscString(const std::string& text, const std::string& prefix) 635 { 636 if (text == "") 637 return ""; 638 else 639 return "\n" + prefix + ": " + text; 640 } 641 getCountString(const int value)642 std::string ToolTips::getCountString(const int value) 643 { 644 if (value == 1) 645 return ""; 646 else 647 return " (" + MyGUI::utility::toString(value) + ")"; 648 } 649 getSoulString(const MWWorld::CellRef & cellref)650 std::string ToolTips::getSoulString(const MWWorld::CellRef& cellref) 651 { 652 std::string soul = cellref.getSoul(); 653 if (soul.empty()) 654 return std::string(); 655 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); 656 const ESM::Creature *creature = store.get<ESM::Creature>().search(soul); 657 if (!creature) 658 return std::string(); 659 if (creature->mName.empty()) 660 return " (" + creature->mId + ")"; 661 return " (" + creature->mName + ")"; 662 } 663 getCellRefString(const MWWorld::CellRef & cellref)664 std::string ToolTips::getCellRefString(const MWWorld::CellRef& cellref) 665 { 666 std::string ret; 667 ret += getMiscString(cellref.getOwner(), "Owner"); 668 const std::string factionId = cellref.getFaction(); 669 if (!factionId.empty()) 670 { 671 const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); 672 const ESM::Faction *fact = store.get<ESM::Faction>().search(factionId); 673 if (fact != nullptr) 674 { 675 ret += getMiscString(fact->mName.empty() ? factionId : fact->mName, "Owner Faction"); 676 if (cellref.getFactionRank() >= 0) 677 { 678 int rank = cellref.getFactionRank(); 679 const std::string rankName = fact->mRanks[rank]; 680 if (rankName.empty()) 681 ret += getValueString(cellref.getFactionRank(), "Rank"); 682 else 683 ret += getMiscString(rankName, "Rank"); 684 } 685 } 686 } 687 688 std::vector<std::pair<std::string, int> > itemOwners = 689 MWBase::Environment::get().getMechanicsManager()->getStolenItemOwners(cellref.getRefId()); 690 691 for (std::pair<std::string, int>& owner : itemOwners) 692 { 693 if (owner.second == std::numeric_limits<int>::max()) 694 ret += std::string("\nStolen from ") + owner.first; // for legacy (ESS) savegames 695 else 696 ret += std::string("\nStolen ") + MyGUI::utility::toString(owner.second) + " from " + owner.first; 697 } 698 699 ret += getMiscString(cellref.getGlobalVariable(), "Global"); 700 return ret; 701 } 702 getDurationString(float duration,const std::string & prefix)703 std::string ToolTips::getDurationString(float duration, const std::string& prefix) 704 { 705 std::string ret; 706 ret = prefix + ": "; 707 708 if (duration < 1.f) 709 { 710 ret += "0 s"; 711 return ret; 712 } 713 714 constexpr int secondsPerMinute = 60; // 60 seconds 715 constexpr int secondsPerHour = secondsPerMinute * 60; // 60 minutes 716 constexpr int secondsPerDay = secondsPerHour * 24; // 24 hours 717 constexpr int secondsPerMonth = secondsPerDay * 30; // 30 days 718 constexpr int secondsPerYear = secondsPerDay * 365; 719 int fullDuration = static_cast<int>(duration); 720 int units = 0; 721 int years = fullDuration / secondsPerYear; 722 int months = fullDuration % secondsPerYear / secondsPerMonth; 723 int days = fullDuration % secondsPerYear % secondsPerMonth / secondsPerDay; // Because a year is not exactly 12 "months" 724 int hours = fullDuration % secondsPerDay / secondsPerHour; 725 int minutes = fullDuration % secondsPerHour / secondsPerMinute; 726 int seconds = fullDuration % secondsPerMinute; 727 if (years) 728 { 729 units++; 730 ret += toString(years) + " y "; 731 } 732 if (months) 733 { 734 units++; 735 ret += toString(months) + " mo "; 736 } 737 if (units < 2 && days) 738 { 739 units++; 740 ret += toString(days) + " d "; 741 } 742 if (units < 2 && hours) 743 { 744 units++; 745 ret += toString(hours) + " h "; 746 } 747 if (units >= 2) 748 return ret; 749 if (minutes) 750 ret += toString(minutes) + " min "; 751 if (seconds) 752 ret += toString(seconds) + " s "; 753 754 return ret; 755 } 756 toggleFullHelp()757 bool ToolTips::toggleFullHelp() 758 { 759 mFullHelp = !mFullHelp; 760 return mFullHelp; 761 } 762 getFullHelp() const763 bool ToolTips::getFullHelp() const 764 { 765 return mFullHelp; 766 } 767 setFocusObjectScreenCoords(float min_x,float min_y,float max_x,float max_y)768 void ToolTips::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) 769 { 770 mFocusToolTipX = (min_x + max_x) / 2; 771 mFocusToolTipY = min_y; 772 } 773 createSkillToolTip(MyGUI::Widget * widget,int skillId)774 void ToolTips::createSkillToolTip(MyGUI::Widget* widget, int skillId) 775 { 776 if (skillId == -1) 777 return; 778 779 const MWWorld::ESMStore &store = 780 MWBase::Environment::get().getWorld()->getStore(); 781 782 const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; 783 const ESM::Skill* skill = store.get<ESM::Skill>().find(skillId); 784 assert(skill); 785 786 const ESM::Attribute* attr = 787 store.get<ESM::Attribute>().find(skill->mData.mAttribute); 788 assert(attr); 789 std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; 790 791 widget->setUserString("ToolTipType", "Layout"); 792 widget->setUserString("ToolTipLayout", "SkillNoProgressToolTip"); 793 widget->setUserString("Caption_SkillNoProgressName", "#{"+skillNameId+"}"); 794 widget->setUserString("Caption_SkillNoProgressDescription", skill->mDescription); 795 widget->setUserString("Caption_SkillNoProgressAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}"); 796 widget->setUserString("ImageTexture_SkillNoProgressImage", icon); 797 } 798 createAttributeToolTip(MyGUI::Widget * widget,int attributeId)799 void ToolTips::createAttributeToolTip(MyGUI::Widget* widget, int attributeId) 800 { 801 if (attributeId == -1) 802 return; 803 804 std::string icon = ESM::Attribute::sAttributeIcons[attributeId]; 805 std::string name = ESM::Attribute::sGmstAttributeIds[attributeId]; 806 std::string desc = ESM::Attribute::sGmstAttributeDescIds[attributeId]; 807 808 widget->setUserString("ToolTipType", "Layout"); 809 widget->setUserString("ToolTipLayout", "AttributeToolTip"); 810 widget->setUserString("Caption_AttributeName", "#{"+name+"}"); 811 widget->setUserString("Caption_AttributeDescription", "#{"+desc+"}"); 812 widget->setUserString("ImageTexture_AttributeImage", icon); 813 } 814 createSpecializationToolTip(MyGUI::Widget * widget,const std::string & name,int specId)815 void ToolTips::createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId) 816 { 817 widget->setUserString("Caption_Caption", name); 818 std::string specText; 819 // get all skills of this specialisation 820 const MWWorld::Store<ESM::Skill> &skills = 821 MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>(); 822 823 bool isFirst = true; 824 for (auto& skillPair : skills) 825 { 826 if (skillPair.second.mData.mSpecialization == specId) 827 { 828 if (isFirst) 829 isFirst = false; 830 else 831 specText += "\n"; 832 833 specText += std::string("#{") + ESM::Skill::sSkillNameIds[skillPair.first] + "}"; 834 } 835 } 836 widget->setUserString("Caption_ColumnText", specText); 837 widget->setUserString("ToolTipLayout", "SpecializationToolTip"); 838 widget->setUserString("ToolTipType", "Layout"); 839 } 840 createBirthsignToolTip(MyGUI::Widget * widget,const std::string & birthsignId)841 void ToolTips::createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId) 842 { 843 const MWWorld::ESMStore &store = 844 MWBase::Environment::get().getWorld()->getStore(); 845 846 const ESM::BirthSign *sign = store.get<ESM::BirthSign>().find(birthsignId); 847 848 widget->setUserString("ToolTipType", "Layout"); 849 widget->setUserString("ToolTipLayout", "BirthSignToolTip"); 850 widget->setUserString("ImageTexture_BirthSignImage", MWBase::Environment::get().getWindowManager()->correctTexturePath(sign->mTexture)); 851 std::string text; 852 853 text += sign->mName; 854 text += "\n#{fontcolourhtml=normal}" + sign->mDescription; 855 856 std::vector<std::string> abilities, powers, spells; 857 858 for (const std::string& spellId : sign->mPowers.mList) 859 { 860 const ESM::Spell *spell = store.get<ESM::Spell>().search(spellId); 861 if (!spell) 862 continue; // Skip spells which cannot be found 863 ESM::Spell::SpellType type = static_cast<ESM::Spell::SpellType>(spell->mData.mType); 864 if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Ability && type != ESM::Spell::ST_Power) 865 continue; // We only want spell, ability and powers. 866 867 if (type == ESM::Spell::ST_Ability) 868 abilities.push_back(spellId); 869 else if (type == ESM::Spell::ST_Power) 870 powers.push_back(spellId); 871 else if (type == ESM::Spell::ST_Spell) 872 spells.push_back(spellId); 873 } 874 875 struct { 876 const std::vector<std::string> &spells; 877 std::string label; 878 } 879 categories[3] = { 880 {abilities, "sBirthsignmenu1"}, 881 {powers, "sPowers"}, 882 {spells, "sBirthsignmenu2"} 883 }; 884 885 for (int category = 0; category < 3; ++category) 886 { 887 bool addHeader = true; 888 for (const std::string& spellId : categories[category].spells) 889 { 890 if (addHeader) 891 { 892 text += std::string("\n\n#{fontcolourhtml=header}") + std::string("#{") + categories[category].label + "}"; 893 addHeader = false; 894 } 895 896 const ESM::Spell *spell = store.get<ESM::Spell>().find(spellId); 897 text += "\n#{fontcolourhtml=normal}" + spell->mName; 898 } 899 } 900 901 widget->setUserString("Caption_BirthSignText", text); 902 } 903 createRaceToolTip(MyGUI::Widget * widget,const ESM::Race * playerRace)904 void ToolTips::createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace) 905 { 906 widget->setUserString("Caption_CenteredCaption", playerRace->mName); 907 widget->setUserString("Caption_CenteredCaptionText", playerRace->mDescription); 908 widget->setUserString("ToolTipType", "Layout"); 909 widget->setUserString("ToolTipLayout", "RaceToolTip"); 910 } 911 createClassToolTip(MyGUI::Widget * widget,const ESM::Class & playerClass)912 void ToolTips::createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass) 913 { 914 if (playerClass.mName == "") 915 return; 916 917 int spec = playerClass.mData.mSpecialization; 918 std::string specStr; 919 if (spec == 0) 920 specStr = "#{sSpecializationCombat}"; 921 else if (spec == 1) 922 specStr = "#{sSpecializationMagic}"; 923 else if (spec == 2) 924 specStr = "#{sSpecializationStealth}"; 925 926 widget->setUserString("Caption_ClassName", playerClass.mName); 927 widget->setUserString("Caption_ClassDescription", playerClass.mDescription); 928 widget->setUserString("Caption_ClassSpecialisation", "#{sSpecialization}: " + specStr); 929 widget->setUserString("ToolTipType", "Layout"); 930 widget->setUserString("ToolTipLayout", "ClassToolTip"); 931 } 932 createMagicEffectToolTip(MyGUI::Widget * widget,short id)933 void ToolTips::createMagicEffectToolTip(MyGUI::Widget* widget, short id) 934 { 935 const ESM::MagicEffect* effect = 936 MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(id); 937 const std::string &name = ESM::MagicEffect::effectIdToString (id); 938 939 std::string icon = effect->mIcon; 940 int slashPos = icon.rfind('\\'); 941 icon.insert(slashPos+1, "b_"); 942 icon = MWBase::Environment::get().getWindowManager()->correctIconPath(icon); 943 944 widget->setUserString("ToolTipType", "Layout"); 945 widget->setUserString("ToolTipLayout", "MagicEffectToolTip"); 946 widget->setUserString("Caption_MagicEffectName", "#{" + name + "}"); 947 widget->setUserString("Caption_MagicEffectDescription", effect->mDescription); 948 widget->setUserString("Caption_MagicEffectSchool", "#{sSchool}: " + sSchoolNames[effect->mData.mSchool]); 949 widget->setUserString("ImageTexture_MagicEffectImage", icon); 950 } 951 setDelay(float delay)952 void ToolTips::setDelay(float delay) 953 { 954 mDelay = delay; 955 mRemainingDelay = mDelay; 956 } 957 958 } 959