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