/* This file is part of Telegram Desktop, the official desktop version of Telegram messaging app, see https://telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It 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 General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once void historyInitMedia(); class RadialAnimation { public: RadialAnimation(AnimationCallbacks &&callbacks); float64 opacity() const { return _opacity; } bool animating() const { return _animation.animating(); } void start(float64 prg); void update(float64 prg, bool finished, uint64 ms); void stop(); void step(uint64 ms); void step() { step(getms()); } void draw(Painter &p, const QRect &inner, int32 thickness, const style::color &color); private: uint64 _firstStart = 0; uint64 _lastStart = 0; uint64 _lastTime = 0; float64 _opacity = 0.; anim::ivalue a_arcEnd, a_arcStart; Animation _animation; }; class HistoryMedia : public HistoryElement { public: HistoryMedia(HistoryItem *parent) : _parent(parent) { } virtual HistoryMediaType type() const = 0; virtual QString notificationText() const { return QString(); } // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" virtual QString inDialogsText() const; virtual TextWithEntities selectedText(TextSelection selection) const = 0; bool hasPoint(int x, int y) const { return (x >= 0 && y >= 0 && x < _width && y < _height); } virtual bool isDisplayed() const { return true; } virtual bool isAboveMessage() const { return false; } virtual bool hasTextForCopy() const { return false; } virtual void initDimensions() = 0; virtual int resizeGetHeight(int width) { _width = qMin(width, _maxw); return _height; } virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0; virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; // if we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link virtual bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const = 0; // if we press and drag on this media should we drag the item virtual bool dragItem() const { return false; } virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const { return selection; } // if we press and drag this link should we drag the item virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0; virtual void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { } virtual void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { } virtual bool uploading() const { return false; } virtual HistoryMedia *clone(HistoryItem *newParent) const = 0; virtual DocumentData *getDocument() { return nullptr; } virtual Media::Clip::Reader *getClipReader() { return nullptr; } bool playInline(/*bool autoplay = false*/) { return playInline(false); } virtual bool playInline(bool autoplay) { return false; } virtual void stopInline() { } virtual void attachToParent() { } virtual void detachFromParent() { } virtual void updateSentMedia(const MTPMessageMedia &media) { } // After sending an inline result we may want to completely recreate // the media (all media that was generated on client side, for example) virtual bool needReSetInlineResultMedia(const MTPMessageMedia &media) { return true; } virtual bool animating() const { return false; } virtual bool hasReplyPreview() const { return false; } virtual ImagePtr replyPreview() { return ImagePtr(); } virtual TextWithEntities getCaption() const { return TextWithEntities(); } virtual bool needsBubble() const = 0; virtual bool customInfoLayout() const = 0; virtual QMargins bubbleMargins() const { return QMargins(); } virtual bool hideFromName() const { return false; } virtual bool hideForwardedFrom() const { return false; } int currentWidth() const { return _width; } protected: HistoryItem *_parent; int _width = 0; }; class HistoryFileMedia : public HistoryMedia { public: using HistoryMedia::HistoryMedia; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return p == _openl || p == _savel || p == _cancell; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return p == _openl || p == _savel || p == _cancell; } void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; ~HistoryFileMedia(); protected: ClickHandlerPtr _openl, _savel, _cancell; void setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell); void setDocumentLinks(DocumentData *document, bool inlinegif = false) { ClickHandlerPtr open, save; if (inlinegif) { open.reset(new GifOpenClickHandler(document)); } else { open.reset(new DocumentOpenClickHandler(document)); } if (inlinegif) { save.reset(new GifOpenClickHandler(document)); } else if (document->voice()) { save.reset(new DocumentOpenClickHandler(document)); } else { save.reset(new DocumentSaveClickHandler(document)); } setLinks(std_::move(open), std_::move(save), MakeShared(document)); } // >= 0 will contain download / upload string, _statusSize = loaded bytes // < 0 will contain played string, _statusSize = -(seconds + 1) played // 0x7FFFFFF0 will contain status for not yet downloaded file // 0x7FFFFFF1 will contain status for already downloaded file // 0x7FFFFFF2 will contain status for failed to download / upload file mutable int32 _statusSize; mutable QString _statusText; // duration = -1 - no duration, duration = -2 - "GIF" duration void setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const; void step_thumbOver(float64 ms, bool timer); void step_radial(uint64 ms, bool timer); void ensureAnimation() const; void checkAnimationFinished(); bool isRadialAnimation(uint64 ms) const { if (!_animation || !_animation->radial.animating()) return false; _animation->radial.step(ms); return _animation && _animation->radial.animating(); } bool isThumbAnimation(uint64 ms) const { if (!_animation || !_animation->_a_thumbOver.animating()) return false; _animation->_a_thumbOver.step(ms); return _animation && _animation->_a_thumbOver.animating(); } virtual float64 dataProgress() const = 0; virtual bool dataFinished() const = 0; virtual bool dataLoaded() const = 0; struct AnimationData { AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0) , _a_thumbOver(std_::move(thumbOverCallbacks)) , radial(std_::move(radialCallbacks)) { } anim::fvalue a_thumbOver; Animation _a_thumbOver; RadialAnimation radial; }; mutable AnimationData *_animation = nullptr; }; class HistoryPhoto : public HistoryFileMedia { public: HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString &caption); HistoryPhoto(HistoryItem *parent, PeerData *chat, const MTPDphoto &photo, int width); HistoryPhoto(HistoryItem *parent, const HistoryPhoto &other); void init(); HistoryMediaType type() const override { return MediaTypePhoto; } HistoryPhoto *clone(HistoryItem *newParent) const override { return new HistoryPhoto(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; PhotoData *photo() const { return _data; } void updateSentMedia(const MTPMessageMedia &media) override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; void attachToParent() override; void detachFromParent() override; bool hasReplyPreview() const override { return !_data->thumb->isNull(); } ImagePtr replyPreview() override; TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override { if (!_caption.isEmpty()) { return true; } if (_parent->viaBot()) { return true; } return (_parent->Has() || _parent->Has()); } bool customInfoLayout() const override { return _caption.isEmpty(); } bool hideFromName() const override { return true; } protected: float64 dataProgress() const override { return _data->progress(); } bool dataFinished() const override { return !_data->loading() && !_data->uploading(); } bool dataLoaded() const override { return _data->loaded(); } private: PhotoData *_data; int16 _pixw = 1; int16 _pixh = 1; Text _caption; }; class HistoryVideo : public HistoryFileMedia { public: HistoryVideo(HistoryItem *parent, DocumentData *document, const QString &caption); HistoryVideo(HistoryItem *parent, const HistoryVideo &other); HistoryMediaType type() const override { return MediaTypeVideo; } HistoryVideo *clone(HistoryItem *newParent) const override { return new HistoryVideo(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; DocumentData *getDocument() override { return _data; } bool uploading() const override { return _data->uploading(); } void attachToParent() override; void detachFromParent() override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; bool hasReplyPreview() const override { return !_data->thumb->isNull(); } ImagePtr replyPreview() override; TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override { if (!_caption.isEmpty()) { return true; } if (_parent->viaBot()) { return true; } return (_parent->Has() || _parent->Has()); } bool customInfoLayout() const override { return _caption.isEmpty(); } bool hideFromName() const override { return true; } protected: float64 dataProgress() const override { return _data->progress(); } bool dataFinished() const override { return !_data->loading() && !_data->uploading(); } bool dataLoaded() const override { return _data->loaded(); } private: DocumentData *_data; int32 _thumbw; Text _caption; void setStatusSize(int32 newSize) const; void updateStatusText() const; }; struct HistoryDocumentThumbed : public BaseComponent { ClickHandlerPtr _linksavel, _linkcancell; int _thumbw = 0; mutable int _linkw = 0; mutable QString _link; }; struct HistoryDocumentCaptioned : public BaseComponent { Text _caption = { int(st::msgFileMinWidth) - st::msgPadding.left() - st::msgPadding.right() }; }; struct HistoryDocumentNamed : public BaseComponent { QString _name; int _namew = 0; }; class HistoryDocument; struct HistoryDocumentVoicePlayback { HistoryDocumentVoicePlayback(const HistoryDocument *that); int32 _position; anim::fvalue a_progress; Animation _a_progress; }; struct HistoryDocumentVoice : public BaseComponent { HistoryDocumentVoice &operator=(HistoryDocumentVoice &&other) { std::swap(_playback, other._playback); return *this; } ~HistoryDocumentVoice() { deleteAndMark(_playback); } void ensurePlayback(const HistoryDocument *interfaces) const; void checkPlaybackFinished() const; mutable HistoryDocumentVoicePlayback *_playback = nullptr; }; class HistoryDocument : public HistoryFileMedia, public Composer { public: HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption); HistoryDocument(HistoryItem *parent, const HistoryDocument &other); HistoryMediaType type() const override { return _data->voice() ? MediaTypeVoiceFile : (_data->song() ? MediaTypeMusicFile : MediaTypeFile); } HistoryDocument *clone(HistoryItem *newParent) const override { return new HistoryDocument(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { if (auto captioned = Get()) { return captioned->_caption.adjustSelection(selection, type); } return selection; } bool hasTextForCopy() const override { return Has(); } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; bool uploading() const override { return _data->uploading(); } DocumentData *getDocument() override { return _data; } void attachToParent() override; void detachFromParent() override; void updateSentMedia(const MTPMessageMedia &media) override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; bool hasReplyPreview() const override { return !_data->thumb->isNull(); } ImagePtr replyPreview() override; TextWithEntities getCaption() const override { if (const HistoryDocumentCaptioned *captioned = Get()) { return captioned->_caption.originalTextWithEntities(); } return TextWithEntities(); } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } QMargins bubbleMargins() const override { return Get() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding; } bool hideForwardedFrom() const override { return _data->song(); } void step_voiceProgress(float64 ms, bool timer); protected: float64 dataProgress() const override { return _data->progress(); } bool dataFinished() const override { return !_data->loading() && !_data->uploading(); } bool dataLoaded() const override { return _data->loaded(); } private: void createComponents(bool caption); void setStatusSize(int32 newSize, qint64 realDuration = 0) const; bool updateStatusText() const; // returns showPause // Callback is a void(const QString &, const QString &, const Text &) functor. // It will be called as callback(attachType, attachFileName, attachCaption). template void buildStringRepresentation(Callback callback) const; DocumentData *_data; }; class HistoryGif : public HistoryFileMedia { public: HistoryGif(HistoryItem *parent, DocumentData *document, const QString &caption); HistoryGif(HistoryItem *parent, const HistoryGif &other); HistoryMediaType type() const override { return MediaTypeGif; } HistoryGif *clone(HistoryItem *newParent) const override { return new HistoryGif(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; bool uploading() const override { return _data->uploading(); } DocumentData *getDocument() override { return _data; } Media::Clip::Reader *getClipReader() override { return gif(); } bool playInline(bool autoplay) override; void stopInline() override; void attachToParent() override; void detachFromParent() override; void updateSentMedia(const MTPMessageMedia &media) override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; bool hasReplyPreview() const override { return !_data->thumb->isNull(); } ImagePtr replyPreview() override; TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override { if (!_caption.isEmpty()) { return true; } if (_parent->viaBot()) { return true; } return (_parent->Has() || _parent->Has()); } bool customInfoLayout() const override { return _caption.isEmpty(); } bool hideFromName() const override { return true; } ~HistoryGif(); protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; private: DocumentData *_data; int32 _thumbw, _thumbh; Text _caption; Media::Clip::Reader *_gif; Media::Clip::Reader *gif() { return (_gif == Media::Clip::BadReader) ? nullptr : _gif; } const Media::Clip::Reader *gif() const { return (_gif == Media::Clip::BadReader) ? nullptr : _gif; } void setStatusSize(int32 newSize) const; void updateStatusText() const; }; class HistorySticker : public HistoryMedia { public: HistorySticker(HistoryItem *parent, DocumentData *document); HistoryMediaType type() const override { return MediaTypeSticker; } HistorySticker *clone(HistoryItem *newParent) const override { return new HistorySticker(newParent, _data); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; } bool dragItem() const override { return true; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return true; } QString notificationText() const override; TextWithEntities selectedText(TextSelection selection) const override; DocumentData *getDocument() override { return _data; } void attachToParent() override; void detachFromParent() override; void updateSentMedia(const MTPMessageMedia &media) override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; bool needsBubble() const override { return false; } bool customInfoLayout() const override { return true; } private: int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const; int additionalWidth() const { return additionalWidth(_parent->Get(), _parent->Get()); } QString toString() const; int16 _pixw, _pixh; ClickHandlerPtr _packLink; DocumentData *_data; QString _emoji; }; class SendMessageClickHandler : public PeerClickHandler { public: using PeerClickHandler::PeerClickHandler; protected: void onClickImpl() const override; }; class AddContactClickHandler : public MessageClickHandler { public: using MessageClickHandler::MessageClickHandler; protected: void onClickImpl() const override; }; class HistoryContact : public HistoryMedia { public: HistoryContact(HistoryItem *parent, int32 userId, const QString &first, const QString &last, const QString &phone); HistoryMediaType type() const override { return MediaTypeContact; } HistoryContact *clone(HistoryItem *newParent) const override { return new HistoryContact(newParent, _userId, _fname, _lname, _phone); } void initDimensions() override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return true; } QString notificationText() const override; TextWithEntities selectedText(TextSelection selection) const override; void attachToParent() override; void detachFromParent() override; void updateSentMedia(const MTPMessageMedia &media) override; bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } const QString &fname() const { return _fname; } const QString &lname() const { return _lname; } const QString &phone() const { return _phone; } private: int32 _userId; UserData *_contact; int32 _phonew; QString _fname, _lname, _phone; Text _name; ClickHandlerPtr _linkl; int32 _linkw; QString _link; }; class HistoryWebPage : public HistoryMedia { public: HistoryWebPage(HistoryItem *parent, WebPageData *data); HistoryWebPage(HistoryItem *parent, const HistoryWebPage &other); HistoryMediaType type() const override { return MediaTypeWebPage; } HistoryWebPage *clone(HistoryItem *newParent) const override { return new HistoryWebPage(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool hasTextForCopy() const override { return false; // we do not add _title and _description in FullSelection text copy. } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return _attach && _attach->toggleSelectionByHandlerClick(p); } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return _attach && _attach->dragItemByHandler(p); } TextWithEntities selectedText(TextSelection selection) const override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; bool isDisplayed() const override { return !_data->pendingTill; } DocumentData *getDocument() override { return _attach ? _attach->getDocument() : 0; } Media::Clip::Reader *getClipReader() override { return _attach ? _attach->getClipReader() : 0; } bool playInline(bool autoplay) override { return _attach ? _attach->playInline(autoplay) : false; } void stopInline() override { if (_attach) _attach->stopInline(); } void attachToParent() override; void detachFromParent() override; bool hasReplyPreview() const override { return (_data->photo && !_data->photo->thumb->isNull()) || (_data->document && !_data->document->thumb->isNull()); } ImagePtr replyPreview() override; WebPageData *webpage() { return _data; } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } HistoryMedia *attach() const { return _attach.get(); } private: TextSelection toDescriptionSelection(TextSelection selection) const { return internal::unshiftSelection(selection, _title); } TextSelection fromDescriptionSelection(TextSelection selection) const { return internal::shiftSelection(selection, _title); } WebPageData *_data; ClickHandlerPtr _openl; std_::unique_ptr _attach; bool _asArticle = false; int32 _titleLines, _descriptionLines; Text _title, _description; int32 _siteNameWidth = 0; QString _duration; int32 _durationWidth = 0; int16 _pixw = 0; int16 _pixh = 0; }; class HistoryGame : public HistoryMedia { public: HistoryGame(HistoryItem *parent, GameData *data); HistoryGame(HistoryItem *parent, const HistoryGame &other); HistoryMediaType type() const override { return MediaTypeGame; } HistoryGame *clone(HistoryItem *newParent) const override { return new HistoryGame(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool isAboveMessage() const override { return true; } bool hasTextForCopy() const override { return false; // we do not add _title and _description in FullSelection text copy. } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return _attach && _attach->toggleSelectionByHandlerClick(p); } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return _attach && _attach->dragItemByHandler(p); } TextWithEntities selectedText(TextSelection selection) const override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; DocumentData *getDocument() override { return _attach ? _attach->getDocument() : nullptr; } Media::Clip::Reader *getClipReader() override { return _attach ? _attach->getClipReader() : nullptr; } bool playInline(bool autoplay) override { return _attach ? _attach->playInline(autoplay) : false; } void stopInline() override { if (_attach) _attach->stopInline(); } void attachToParent() override; void detachFromParent() override; bool hasReplyPreview() const override { return (_data->photo && !_data->photo->thumb->isNull()) || (_data->document && !_data->document->thumb->isNull()); } ImagePtr replyPreview() override; GameData *game() { return _data; } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } HistoryMedia *attach() const { return _attach.get(); } private: TextSelection toDescriptionSelection(TextSelection selection) const { return internal::unshiftSelection(selection, _title); } TextSelection fromDescriptionSelection(TextSelection selection) const { return internal::shiftSelection(selection, _title); } GameData *_data; ClickHandlerPtr _openl; std_::unique_ptr _attach; int32 _titleLines, _descriptionLines; Text _title, _description; }; struct LocationCoords; struct LocationData; class HistoryLocation : public HistoryMedia { public: HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString()); HistoryLocation(HistoryItem *parent, const HistoryLocation &other); HistoryMediaType type() const override { return MediaTypeLocation; } HistoryLocation *clone(HistoryItem *newParent) const override { return new HistoryLocation(newParent, *this); } void initDimensions() override; int resizeGetHeight(int32 width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool hasTextForCopy() const override { return !_title.isEmpty() || !_description.isEmpty(); } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return p == _link; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return p == _link; } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; bool needsBubble() const override { if (!_title.isEmpty() || !_description.isEmpty()) { return true; } if (_parent->viaBot()) { return true; } return (_parent->Has() || _parent->Has()); } bool customInfoLayout() const override { return true; } private: TextSelection toDescriptionSelection(TextSelection selection) const { return internal::unshiftSelection(selection, _title); } TextSelection fromDescriptionSelection(TextSelection selection) const { return internal::shiftSelection(selection, _title); } LocationData *_data; Text _title, _description; ClickHandlerPtr _link; int32 fullWidth() const; int32 fullHeight() const; };