diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png index bb18b32eaa..179237f54a 100644 Binary files a/Telegram/Resources/art/sprite.png and b/Telegram/Resources/art/sprite.png differ diff --git a/Telegram/Resources/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png index 95f4086129..e5dd6795e5 100644 Binary files a/Telegram/Resources/art/sprite_200x.png and b/Telegram/Resources/art/sprite_200x.png differ diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 55c6121ca0..e44c89973b 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -1232,7 +1232,7 @@ introErrLabelTextStyle: textStyle(defaultTextStyle) { mediaPadding: margins(0px, 0px, 0px, 0px);//1px, 1px, 1px, 1px);//2px, 2px, 2px, 2px); mediaCaptionSkip: 5px; -mediaHeaderSkip: 5px; +mediaInBubbleSkip: 5px; mediaThumbSize: 48px; mediaNameTop: 3px; mediaDetailsShift: 3px; @@ -2285,7 +2285,6 @@ webPageLeft: 10px; webPageBar: 2px; webPageTitleFont: semiboldFont; webPageDescriptionFont: normalFont; -webPagePhotoSkip: 5px; webPagePhotoSize: 100px; webPagePhotoDelta: 8px; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index fcdfe3dcf6..b7758130ef 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -31,6 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "data/data_abstract_structure.h" #include "history/history_service_layout.h" #include "history/history_location_manager.h" +#include "history/history_media_types.h" #include "media/media_audio.h" #include "inline_bots/inline_bot_layout_item.h" #include "application.h" @@ -1523,7 +1524,7 @@ namespace { } GameData *feedGame(const MTPDgame &game, GameData *convert) { - return App::gameSet(game.vid.v, convert, game.vaccess_hash.v, qs(game.vshort_name), qs(game.vtitle), qs(game.vdescription), qs(game.vurl), App::feedPhoto(game.vphoto), game.has_document() ? App::feedDocument(game.vdocument) : nullptr); + return App::gameSet(game.vid.v, convert, game.vaccess_hash.v, qs(game.vshort_name), qs(game.vtitle), qs(game.vdescription), App::feedPhoto(game.vphoto), game.has_document() ? App::feedDocument(game.vdocument) : nullptr); } UserData *curUser() { @@ -1847,7 +1848,7 @@ namespace { return i.value(); } - GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, const QString &url, PhotoData *photo, DocumentData *document) { + GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document) { if (convert) { if (convert->id != game) { auto i = gamesData.find(convert->id); @@ -1856,12 +1857,11 @@ namespace { } convert->id = game; } - if (convert->url.isEmpty() && !url.isEmpty()) { + if (convert->shortName.isEmpty() && !shortName.isEmpty()) { convert->accessHash = accessHash; convert->shortName = shortName; convert->title = title; convert->description = description; - convert->url = url; convert->photo = photo; convert->document = document; if (App::main()) App::main()->gameUpdated(convert); @@ -1873,18 +1873,17 @@ namespace { if (convert) { result = convert; } else { - result = new GameData(game, accessHash, shortName, title, description, url, photo, document); + result = new GameData(game, accessHash, shortName, title, description, photo, document); } gamesData.insert(game, result); } else { result = i.value(); if (result != convert) { - if (result->url.isEmpty() && !url.isEmpty()) { + if (result->shortName.isEmpty() && !shortName.isEmpty()) { result->accessHash = accessHash; result->shortName = shortName; result->title = title; result->description = description; - result->url = url; result->photo = photo; result->document = document; if (App::main()) App::main()->gameUpdated(result); diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index a1e11bf735..466fec3961 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -44,6 +44,9 @@ using GifItems = QHash; using PhotosData = QHash; using DocumentsData = QHash; +struct LocationCoords; +struct LocationData; + namespace App { AppClass *app(); MainWindow *wnd(); @@ -154,7 +157,7 @@ namespace App { WebPageData *webPage(const WebPageId &webPage); WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill); GameData *game(const GameId &game); - GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, const QString &url, PhotoData *photo, DocumentData *doc); + GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc); LocationData *location(const LocationCoords &coords); void forgetMedia(); diff --git a/Telegram/SourceFiles/boxes/photosendbox.cpp b/Telegram/SourceFiles/boxes/photosendbox.cpp index 9bc20e7ba3..ff3c66b180 100644 --- a/Telegram/SourceFiles/boxes/photosendbox.cpp +++ b/Telegram/SourceFiles/boxes/photosendbox.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "mainwidget.h" #include "photosendbox.h" +#include "history/history_media_types.h" PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxWideWidth) , _file(file) diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index be1e0bdaeb..56b7b974e9 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "history.h" +#include "history/history_media_types.h" #include "dialogs/dialogs_indexed_list.h" #include "styles/style_dialogs.h" #include "data/data_drafts.h" diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 89bd45da87..6b71261e3e 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -28,84 +28,64 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_dialogs.h" #include "fileuploader.h" -class ReplyMarkupClickHandler : public LeftButtonClickHandler { -public: - ReplyMarkupClickHandler(const HistoryItem *item, int row, int col) : _itemId(item->fullId()), _row(row), _col(col) { - } +ReplyMarkupClickHandler::ReplyMarkupClickHandler(const HistoryItem *item, int row, int col) +: _itemId(item->fullId()) +, _row(row) +, _col(col) { +} - QString tooltip() const override { - return _fullDisplayed ? QString() : buttonText(); +// Copy to clipboard support. +void ReplyMarkupClickHandler::copyToClipboard() const { + if (auto button = getButton()) { + if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) { + auto url = QString::fromUtf8(button->data); + if (!url.isEmpty()) { + QApplication::clipboard()->setText(url); + } + } } +} - void setFullDisplayed(bool full) { - _fullDisplayed = full; +QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const { + if (auto button = getButton()) { + if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) { + return lang(lng_context_copy_link); + } } + return QString(); +} - // Copy to clipboard support. - void copyToClipboard() const override { - if (auto button = getButton()) { - if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) { - auto url = QString::fromUtf8(button->data); - if (!url.isEmpty()) { - QApplication::clipboard()->setText(url); +// Finds the corresponding button in the items markup struct. +// If the button is not found it returns nullptr. +// Note: it is possible that we will point to the different button +// than the one was used when constructing the handler, but not a big deal. +const HistoryMessageReplyMarkup::Button *ReplyMarkupClickHandler::getButton() const { + if (auto item = App::histItemById(_itemId)) { + if (auto markup = item->Get()) { + if (_row < markup->rows.size()) { + auto &row = markup->rows.at(_row); + if (_col < row.size()) { + return &row.at(_col); } } } } - QString copyToClipboardContextItemText() const override { - if (auto button = getButton()) { - if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) { - return lang(lng_context_copy_link); - } - } - return QString(); + return nullptr; +} + +void ReplyMarkupClickHandler::onClickImpl() const { + if (auto item = App::histItemById(_itemId)) { + App::activateBotCommand(item, _row, _col); } +} - // Finds the corresponding button in the items markup struct. - // If the button is not found it returns nullptr. - // Note: it is possible that we will point to the different button - // than the one was used when constructing the handler, but not a big deal. - const HistoryMessageReplyMarkup::Button *getButton() const { - if (auto item = App::histItemById(_itemId)) { - if (auto markup = item->Get()) { - if (_row < markup->rows.size()) { - auto &row = markup->rows.at(_row); - if (_col < row.size()) { - return &row.at(_col); - } - } - } - } - return nullptr; +// Returns the full text of the corresponding button. +QString ReplyMarkupClickHandler::buttonText() const { + if (auto button = getButton()) { + return button->text; } - - // We hold only FullMsgId, not HistoryItem*, because all click handlers - // are activated async and the item may be already destroyed. - void setMessageId(const FullMsgId &msgId) { - _itemId = msgId; - } - -protected: - void onClickImpl() const override { - if (auto item = App::histItemById(_itemId)) { - App::activateBotCommand(item, _row, _col); - } - } - -private: - FullMsgId _itemId; - int _row, _col; - bool _fullDisplayed = true; - - // Returns the full text of the corresponding button. - QString buttonText() const { - if (auto button = getButton()) { - return button->text; - } - return QString(); - } - -}; + return QString(); +} ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s) : _item(item) diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index fb82f9193c..008eb9b13f 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -230,7 +230,47 @@ private: }; -class ReplyMarkupClickHandler; +class ReplyMarkupClickHandler : public LeftButtonClickHandler { +public: + ReplyMarkupClickHandler(const HistoryItem *item, int row, int col); + + QString tooltip() const override { + return _fullDisplayed ? QString() : buttonText(); + } + + void setFullDisplayed(bool full) { + _fullDisplayed = full; + } + + // Copy to clipboard support. + void copyToClipboard() const override; + QString copyToClipboardContextItemText() const override; + + // Finds the corresponding button in the items markup struct. + // If the button is not found it returns nullptr. + // Note: it is possible that we will point to the different button + // than the one was used when constructing the handler, but not a big deal. + const HistoryMessageReplyMarkup::Button *getButton() const; + + // We hold only FullMsgId, not HistoryItem*, because all click handlers + // are activated async and the item may be already destroyed. + void setMessageId(const FullMsgId &msgId) { + _itemId = msgId; + } + +protected: + void onClickImpl() const override; + +private: + FullMsgId _itemId; + int _row, _col; + bool _fullDisplayed = true; + + // Returns the full text of the corresponding button. + QString buttonText() const; + +}; + class ReplyKeyboard { private: struct Button; diff --git a/Telegram/SourceFiles/history/history_media.h b/Telegram/SourceFiles/history/history_media.h index a43301f230..c60e407482 100644 --- a/Telegram/SourceFiles/history/history_media.h +++ b/Telegram/SourceFiles/history/history_media.h @@ -20,38 +20,11 @@ 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; - +enum class MediaInBubbleState { + None, + Top, + Middle, + Bottom, }; class HistoryMedia : public HistoryElement { @@ -67,7 +40,10 @@ public: // 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 QString inDialogsText() const { + auto result = notificationText(); + return result.isEmpty() ? QString() : textcmdLink(1, textClean(result)); + } virtual TextWithEntities selectedText(TextSelection selection) const = 0; bool hasPoint(int x, int y) const { @@ -177,881 +153,22 @@ public: return _width; } + void setInBubbleState(MediaInBubbleState state) { + _inBubbleState = state; + } + MediaInBubbleState inBubbleState() const { + return _inBubbleState; + } + bool isBubbleTop() const { + return (_inBubbleState == MediaInBubbleState::Top) || (_inBubbleState == MediaInBubbleState::None); + } + bool isBubbleBottom() const { + return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None); + } + 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; + MediaInBubbleState _inBubbleState = MediaInBubbleState::None; }; diff --git a/Telegram/SourceFiles/history/history_media.cpp b/Telegram/SourceFiles/history/history_media_types.cpp similarity index 90% rename from Telegram/SourceFiles/history/history_media.cpp rename to Telegram/SourceFiles/history/history_media_types.cpp index 991d672411..5b1d7bcc70 100644 --- a/Telegram/SourceFiles/history/history_media.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -19,7 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "history/history_media.h" +#include "history/history_media_types.h" #include "lang.h" #include "mainwidget.h" @@ -86,81 +86,6 @@ void historyInitMedia() { initTextOptions(); } -RadialAnimation::RadialAnimation(AnimationCallbacks &&callbacks) -: a_arcEnd(0, 0) -, a_arcStart(0, FullArcLength) -, _animation(std_::move(callbacks)) { -} - -void RadialAnimation::start(float64 prg) { - _firstStart = _lastStart = _lastTime = getms(); - int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength), iprgstrict = qRound(prg * AlmostFullArcLength); - a_arcEnd = anim::ivalue(iprgstrict, iprg); - _animation.start(); -} - -void RadialAnimation::update(float64 prg, bool finished, uint64 ms) { - int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength); - if (iprg != a_arcEnd.to()) { - a_arcEnd.start(iprg); - _lastStart = _lastTime; - } - _lastTime = ms; - - float64 dt = float64(ms - _lastStart), fulldt = float64(ms - _firstStart); - _opacity = qMin(fulldt / st::radialDuration, 1.); - if (!finished) { - a_arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear); - } else if (dt >= st::radialDuration) { - a_arcEnd.update(1, anim::linear); - stop(); - } else { - float64 r = dt / st::radialDuration; - a_arcEnd.update(r, anim::linear); - _opacity *= 1 - r; - } - float64 fromstart = fulldt / st::radialPeriod; - a_arcStart.update(fromstart - std::floor(fromstart), anim::linear); -} - -void RadialAnimation::stop() { - _firstStart = _lastStart = _lastTime = 0; - a_arcEnd = anim::ivalue(0, 0); - _animation.stop(); -} - -void RadialAnimation::step(uint64 ms) { - _animation.step(ms); -} - -void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, const style::color &color) { - float64 o = p.opacity(); - p.setOpacity(o * _opacity); - - QPen pen(color->p), was(p.pen()); - pen.setWidth(thickness); - p.setPen(pen); - - int32 len = MinArcLength + a_arcEnd.current(); - int32 from = QuarterArcLength - a_arcStart.current() - len; - if (rtl()) { - from = QuarterArcLength - (from - QuarterArcLength) - len; - if (from < 0) from += FullArcLength; - } - - p.setRenderHint(QPainter::HighQualityAntialiasing); - p.drawArc(inner, from, len); - p.setRenderHint(QPainter::HighQualityAntialiasing, false); - - p.setPen(was); - p.setOpacity(o); -} - -QString HistoryMedia::inDialogsText() const { - auto result = notificationText(); - return result.isEmpty() ? QString() : textcmdLink(1, textClean(result)); -} - namespace { int32 documentMaxStatusWidth(DocumentData *document) { @@ -371,7 +296,11 @@ void HistoryPhoto::initDimensions() { _maxw += st::mediaPadding.left() + st::mediaPadding.right(); _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - _minh += st::mediaCaptionSkip + _caption.countHeight(maxActualWidth - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + auto captionw = maxActualWidth - st::msgPadding.left() - st::msgPadding.right(); + _minh += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + _minh += st::msgPadding.bottom(); + } } } } else { @@ -417,7 +346,10 @@ int HistoryPhoto::resizeGetHeight(int width) { _height += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { int captionw = _width - st::msgPadding.left() - st::msgPadding.right(); - _height += st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + _height += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + _height += st::msgPadding.bottom(); + } } } return _height; @@ -425,7 +357,6 @@ int HistoryPhoto::resizeGetHeight(int width) { void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const { if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; - p.fillRect(QRect(0, 0, _width, _height), QColor(128, 255, 128)); _data->automaticLoad(_parent); bool selected = (selection == FullSelection); @@ -453,7 +384,10 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, uin width -= st::mediaPadding.left() + st::mediaPadding.right(); height -= skipy + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - height -= st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + height -= st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + height -= st::msgPadding.bottom(); + } } } else { App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); @@ -470,7 +404,8 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, uin QRect rthumb(rtlrect(skipx, skipy, width, height, _width)); p.drawPixmap(rthumb.topLeft(), pix); if (selected) { - App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayLargeCorners); + auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, overlayCorners); } if (notChild && (radial || (!loaded && !_data->loading()))) { @@ -540,7 +475,10 @@ HistoryTextState HistoryPhoto::getState(int x, int y, HistoryStateRequest reques skipy = st::mediaPadding.top(); if (!_caption.isEmpty()) { int captionw = width - st::msgPadding.left() - st::msgPadding.right(); - height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); + height -= _caption.countHeight(captionw); + if (isBubbleBottom()) { + height -= st::msgPadding.bottom(); + } if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); return result; @@ -712,7 +650,11 @@ void HistoryVideo::initDimensions() { _maxw += st::mediaPadding.left() + st::mediaPadding.right(); _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - _minh += st::mediaCaptionSkip + _caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + auto captionw = _maxw - st::msgPadding.left() - st::msgPadding.right(); + _minh += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + _minh += st::msgPadding.bottom(); + } } } } @@ -750,7 +692,10 @@ int HistoryVideo::resizeGetHeight(int width) { _height += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { int captionw = _width - st::msgPadding.left() - st::msgPadding.right(); - _height += st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + _height += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + _height += st::msgPadding.bottom(); + } } } return _height; @@ -785,16 +730,22 @@ void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, uin width -= st::mediaPadding.left() + st::mediaPadding.right(); height -= skipy + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - height -= st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + height -= st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + height -= st::msgPadding.bottom(); + } } } else { App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } + auto inWebPage = (_parent->getMedia() != this); + auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; QRect rthumb(rtlrect(skipx, skipy, width, height, _width)); - p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(ImageRoundRadius::Large, _thumbw, 0, width, height)); + p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(roundRadius, _thumbw, 0, width, height)); if (selected) { - App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayLargeCorners); + auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, overlayCorners); } QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); @@ -866,8 +817,11 @@ HistoryTextState HistoryVideo::getState(int x, int y, HistoryStateRequest reques skipx = st::mediaPadding.left(); skipy = st::mediaPadding.top(); if (!_caption.isEmpty()) { - int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); - height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); + auto captionw = width - st::msgPadding.left() - st::msgPadding.right(); + height -= _caption.countHeight(captionw); + if (isBubbleBottom()) { + height -= st::msgPadding.bottom(); + } if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); } @@ -1077,7 +1031,11 @@ void HistoryDocument::initDimensions() { } if (captioned) { - _minh += captioned->_caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + auto captionw = _maxw - st::msgPadding.left() - st::msgPadding.right(); + _minh += captioned->_caption.countHeight(captionw); + if (isBubbleBottom()) { + _minh += st::msgPadding.bottom(); + } } else { _height = _minh; } @@ -1095,7 +1053,11 @@ int HistoryDocument::resizeGetHeight(int width) { } else { _height = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom(); } - _height += captioned->_caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + auto captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + _height += captioned->_caption.countHeight(captionw); + if (isBubbleBottom()) { + _height += st::msgPadding.bottom(); + } return _height; } @@ -1129,11 +1091,14 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, linktop = st::msgFileThumbLinkTop; bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom(); + auto inWebPage = (_parent->getMedia() != this); + auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width)); - QPixmap thumb = loaded ? _data->thumb->pixSingle(ImageRoundRadius::Large, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(ImageRoundRadius::Small, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize); + QPixmap thumb = loaded ? _data->thumb->pixSingle(roundRadius, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(ImageRoundRadius::Small, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize); p.drawPixmap(rthumb.topLeft(), thumb); if (selected) { - App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayLargeCorners); + auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, overlayCorners); } if (radial || (!loaded && !_data->loading())) { @@ -1366,7 +1331,11 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req result = captioned->_caption.getState(x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right(), request.forText()); return result; } - height -= captioned->_caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + auto captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + height -= captioned->_caption.countHeight(captionw); + if (isBubbleBottom()) { + height -= st::msgPadding.bottom(); + } } if (x >= 0 && y >= 0 && x < _width && y < height && !_data->loading() && !_data->uploading() && _data->isValid()) { result.link = _openl; @@ -1628,7 +1597,11 @@ void HistoryGif::initDimensions() { _maxw += st::mediaPadding.left() + st::mediaPadding.right(); _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - _minh += st::mediaCaptionSkip + _caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + auto captionw = _maxw - st::msgPadding.left() - st::msgPadding.right(); + _minh += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + _minh += st::msgPadding.bottom(); + } } } } @@ -1685,7 +1658,11 @@ int HistoryGif::resizeGetHeight(int width) { _width += st::mediaPadding.left() + st::mediaPadding.right(); _height += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - _height += st::mediaCaptionSkip + _caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom(); + auto captionw = _width - st::msgPadding.left() - st::msgPadding.right(); + _height += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + _height += st::msgPadding.bottom(); + } } } @@ -1729,7 +1706,10 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6 width -= st::mediaPadding.left() + st::mediaPadding.right(); height -= skipy + st::mediaPadding.bottom(); if (!_caption.isEmpty()) { - height -= st::mediaCaptionSkip + _caption.countHeight(captionw) + st::msgPadding.bottom(); + height -= st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + height -= st::msgPadding.bottom(); + } } } else { App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); @@ -1740,10 +1720,14 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6 if (animating) { p.drawPixmap(rthumb.topLeft(), _gif->current(_thumbw, _thumbh, width, height, (Ui::isLayerShown() || Ui::isMediaViewShown() || Ui::isInlineItemBeingChosen()) ? 0 : ms)); } else { - p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(ImageRoundRadius::Large, _thumbw, _thumbh, width, height)); + auto inWebPage = (_parent->getMedia() != this); + auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large; + p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(roundRadius, _thumbw, _thumbh, width, height)); } if (selected) { - App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayLargeCorners); + auto inWebPage = (_parent->getMedia() != this); + auto overlayCorners = inWebPage ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners; + App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, overlayCorners); } if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == Media::Clip::BadReader)) { @@ -1817,8 +1801,11 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request) skipx = st::mediaPadding.left(); skipy = st::mediaPadding.top(); if (!_caption.isEmpty()) { - int32 captionw = width - st::msgPadding.left() - st::msgPadding.right(); - height -= _caption.countHeight(captionw) + st::msgPadding.bottom(); + auto captionw = width - st::msgPadding.left() - st::msgPadding.right(); + height -= _caption.countHeight(captionw); + if (isBubbleBottom()) { + height -= st::msgPadding.bottom(); + } if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) { result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText()); return result; @@ -2547,25 +2534,31 @@ void HistoryWebPage::initDimensions() { if (_siteNameWidth) { if (_title.isEmpty() && _description.isEmpty()) { - _maxw = qMax(_maxw, int32(_siteNameWidth + _parent->skipBlockWidth())); + accumulate_max(_maxw, _siteNameWidth + _parent->skipBlockWidth()); } else { - _maxw = qMax(_maxw, int32(_siteNameWidth + articlePhotoMaxWidth)); + accumulate_max(_maxw, _siteNameWidth + articlePhotoMaxWidth); } _minh += _lineHeight; } if (!_title.isEmpty()) { - _maxw = qMax(_maxw, int32(_title.maxWidth() + articlePhotoMaxWidth)); + accumulate_max(_maxw, _title.maxWidth() + articlePhotoMaxWidth); _minh += titleMinHeight; } if (!_description.isEmpty()) { - _maxw = qMax(_maxw, int32(_description.maxWidth() + articlePhotoMaxWidth)); + accumulate_max(_maxw, _description.maxWidth() + articlePhotoMaxWidth); _minh += descriptionMinHeight; } if (_attach) { - if (_minh) _minh += st::webPagePhotoSkip; + auto attachAtTop = !_siteNameWidth && _title.isEmpty() && _description.isEmpty(); + if (!attachAtTop) _minh += st::mediaInBubbleSkip; + _attach->initDimensions(); QMargins bubble(_attach->bubbleMargins()); - _maxw = qMax(_maxw, int32(_attach->maxWidth() - bubble.left() - bubble.top() + (_attach->customInfoLayout() ? skipBlockWidth : 0))); + auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); + if (isBubbleBottom() && _attach->customInfoLayout()) { + maxMediaWidth += skipBlockWidth; + } + accumulate_max(_maxw, maxMediaWidth); _minh += _attach->minHeight() - bubble.top() - bubble.bottom(); } if (_data->type == WebPageVideo && _data->duration) { @@ -2573,10 +2566,11 @@ void HistoryWebPage::initDimensions() { _durationWidth = st::msgDateFont->width(_duration); } _maxw += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); - _minh += st::msgPadding.bottom(); + auto padding = inBubblePadding(); + _minh += padding.top() + padding.bottom(); + if (_asArticle) { - _minh = resizeGetHeight(_maxw); // hack - // _minh += st::msgDateFont->height; + _minh = resizeGetHeight(_maxw); } } @@ -2625,7 +2619,7 @@ int HistoryWebPage::resizeGetHeight(int width) { _pixh -= _lineHeight; } while (_pixh > _lineHeight); - _height += st::msgDateFont->height; + _height += bottomInfoPadding(); } else { _height = siteNameHeight; @@ -2653,18 +2647,20 @@ int HistoryWebPage::resizeGetHeight(int width) { } if (_attach) { - if (_height) _height += st::webPagePhotoSkip; + auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; + if (!attachAtTop) _height += st::mediaInBubbleSkip; QMargins bubble(_attach->bubbleMargins()); _attach->resizeGetHeight(width + bubble.left() + bubble.right()); _height += _attach->height() - bubble.top() - bubble.bottom(); - if (_attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { - _height += st::msgDateFont->height; + if (isBubbleBottom() && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + _height += bottomInfoPadding(); } } } - _height += st::msgPadding.bottom(); + auto padding = inBubblePadding(); + _height += padding.top() + padding.bottom(); return _height; } @@ -2680,14 +2676,16 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, u style::color semibold = (selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); style::color regular = (selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); - int32 lshift = st::msgPadding.left() + st::webPageLeft, rshift = st::msgPadding.right(), bshift = st::msgPadding.bottom(); - width -= lshift + rshift; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - if (_asArticle || (_attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right())) { - bshift += st::msgDateFont->height; + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + if (_asArticle || (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right())) { + bshift += bottomInfoPadding(); } + width -= padding.left() + padding.right(); - QRect bar(rtlrect(st::msgPadding.left(), 0, st::webPageBar, _height - bshift, _width)); + QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, _height - tshift - bshift, _width)); p.fillRect(bar, barfg); if (_asArticle) { @@ -2707,17 +2705,16 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, u } else { pix = _data->photo->thumb->pixBlurredSingle(ImageRoundRadius::Small, pixw, pixh, pw, ph); } - p.drawPixmapLeft(lshift + width - pw, 0, _width, pix); + p.drawPixmapLeft(padding.left() + width - pw, 0, _width, pix); if (selected) { - App::roundRect(p, rtlrect(lshift + width - pw, 0, pw, _pixh, _width), textstyleCurrent()->selectOverlay, SelectedOverlaySmallCorners); + App::roundRect(p, rtlrect(padding.left() + width - pw, 0, pw, _pixh, _width), textstyleCurrent()->selectOverlay, SelectedOverlaySmallCorners); } width -= pw + st::webPagePhotoDelta; } - int32 tshift = 0; if (_siteNameWidth) { p.setFont(st::webPageTitleFont); p.setPen(semibold); - p.drawTextLeft(lshift, tshift, _width, (width >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, width)); + p.drawTextLeft(padding.left(), tshift, _width, (width >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, width)); tshift += _lineHeight; } if (_titleLines) { @@ -2726,7 +2723,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, u if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); + _title.drawLeftElided(p, padding.left(), tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); tshift += _titleLines * _lineHeight; } if (_descriptionLines) { @@ -2735,16 +2732,17 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, u if (_description.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); + _description.drawLeftElided(p, padding.left(), tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); tshift += _descriptionLines * _lineHeight; } if (_attach) { - if (tshift) tshift += st::webPagePhotoSkip; + auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; - int32 attachLeft = lshift - bubble.left(), attachTop = tshift - bubble.top(); + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); - p.save(); p.translate(attachLeft, attachTop); auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; @@ -2771,7 +2769,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, u } } - p.restore(); + p.translate(-attachLeft, -attachTop); } } @@ -2781,22 +2779,24 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 skipx = 0, skipy = 0, width = _width, height = _height; - int32 lshift = st::msgPadding.left() + st::webPageLeft, rshift = st::msgPadding.right(), bshift = st::msgPadding.bottom(); - width -= lshift + rshift; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); - if (_asArticle || (_attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right())) { - bshift += st::msgDateFont->height; + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + if (_asArticle || (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right())) { + bshift += bottomInfoPadding(); } + width -= padding.left() + padding.right(); bool inThumb = false; if (_asArticle) { int32 pw = qMax(_pixw, int16(_lineHeight)); - if (rtlrect(lshift + width - pw, 0, pw, _pixh, _width).contains(x, y)) { + if (rtlrect(padding.left() + width - pw, 0, pw, _pixh, _width).contains(x, y)) { inThumb = true; } width -= pw + st::webPagePhotoDelta; } - int tshift = 0, symbolAdd = 0; + int symbolAdd = 0; if (_siteNameWidth) { tshift += _lineHeight; } @@ -2804,7 +2804,7 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ if (y >= tshift && y < tshift + _titleLines * _lineHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; - result = _title.getStateElidedLeft(x - lshift, y - tshift, width, _width, titleRequest); + result = _title.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, titleRequest); } else if (y >= tshift + _titleLines * _lineHeight) { symbolAdd += _title.length(); } @@ -2814,7 +2814,7 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ if (y >= tshift && y < tshift + _descriptionLines * _lineHeight) { Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; - result = _description.getStateElidedLeft(x - lshift, y - tshift, width, _width, descriptionRequest); + result = _description.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, descriptionRequest); } else if (y >= tshift + _descriptionLines * _lineHeight) { symbolAdd += _description.length(); } @@ -2823,10 +2823,12 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ if (inThumb) { result.link = _openl; } else if (_attach) { - if (tshift) tshift += st::webPagePhotoSkip; + auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; - if (x >= lshift && x < lshift + width && y >= tshift && y < _height - st::msgPadding.bottom()) { - int32 attachLeft = lshift - bubble.left(), attachTop = tshift - bubble.top(); + if (x >= padding.left() && x < padding.left() + width && y >= tshift && y < _height - bshift) { + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); result = _attach->getState(x - attachLeft, y - attachTop, request); @@ -2901,6 +2903,28 @@ ImagePtr HistoryWebPage::replyPreview() { return _attach ? _attach->replyPreview() : (_data->photo ? _data->photo->makeReplyPreview() : ImagePtr()); } +QMargins HistoryWebPage::inBubblePadding() const { + auto lshift = st::msgPadding.left() + st::webPageLeft; + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +int HistoryWebPage::bottomInfoPadding() const { + if (!isBubbleBottom()) return 0; + + auto result = st::msgDateFont->height; + + // we use padding greater than st::msgPadding.bottom() in the + // bottom of the bubble so that the left line looks pretty. + // but if we have bottom skip because of the info display + // we don't need that additional padding so we replace it + // back with st::msgPadding.bottom() instead of left(). + result += st::msgPadding.bottom() - st::msgPadding.left(); + return result; +} + HistoryGame::HistoryGame(HistoryItem *parent, GameData *data) : HistoryMedia(parent) , _data(data) , _title(st::msgMinWidth - st::webPageLeft) @@ -2917,7 +2941,7 @@ HistoryGame::HistoryGame(HistoryItem *parent, const HistoryGame &other) : Histor void HistoryGame::initDimensions() { if (!_lineHeight) _lineHeight = qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); - if (!_openl && !_data->url.isEmpty()) _openl.reset(new UrlClickHandler(_data->url, true)); + if (!_openl) _openl.reset(new ReplyMarkupClickHandler(_parent, 0, 0)); auto title = _data->title; @@ -2960,36 +2984,44 @@ void HistoryGame::initDimensions() { int32 l = st::msgPadding.left() + st::webPageLeft, r = st::msgPadding.right(); int32 skipBlockWidth = _parent->skipBlockWidth(); _maxw = skipBlockWidth; - _minh = st::msgPadding.top(); + _minh = 0; int32 titleMinHeight = _title.isEmpty() ? 0 : _lineHeight; int32 descMaxLines = (4 + (titleMinHeight ? 0 : 1)); int32 descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * _lineHeight); if (!_title.isEmpty()) { - _maxw = qMax(_maxw, int32(_title.maxWidth())); + accumulate_max(_maxw, _title.maxWidth()); _minh += titleMinHeight; } if (!_description.isEmpty()) { - _maxw = qMax(_maxw, int32(_description.maxWidth())); + accumulate_max(_maxw, _description.maxWidth()); _minh += descriptionMinHeight; } if (_attach) { - if (_minh) _minh += st::webPagePhotoSkip; + auto attachAtTop = !_titleLines && !_descriptionLines; + if (!attachAtTop) _minh += st::mediaInBubbleSkip; + _attach->initDimensions(); QMargins bubble(_attach->bubbleMargins()); - _maxw = qMax(_maxw, int32(_attach->maxWidth() - bubble.left() - bubble.top())); + auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); + if (isBubbleBottom() && _attach->customInfoLayout()) { + maxMediaWidth += skipBlockWidth; + } + accumulate_max(_maxw, maxMediaWidth); _minh += _attach->minHeight() - bubble.top() - bubble.bottom(); } _maxw += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); + auto padding = inBubblePadding(); + _minh += padding.top() + padding.bottom(); } int HistoryGame::resizeGetHeight(int width) { _width = qMin(width, _maxw); width -= st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); - int32 linesMax = 5; - _height = st::msgPadding.top(); + int linesMax = 5; + _height = 0; if (_title.isEmpty()) { _titleLines = 0; } else { @@ -3014,13 +3046,19 @@ int HistoryGame::resizeGetHeight(int width) { } if (_attach) { - if (_height) _height += st::webPagePhotoSkip; + auto attachAtTop = !_titleLines && !_descriptionLines; + if (!attachAtTop) _height += st::mediaInBubbleSkip; QMargins bubble(_attach->bubbleMargins()); _attach->resizeGetHeight(width + bubble.left() + bubble.right()); _height += _attach->height() - bubble.top() - bubble.bottom(); + if (isBubbleBottom() && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + _height += bottomInfoPadding(); + } } + auto padding = inBubblePadding(); + _height += padding.top() + padding.bottom(); return _height; } @@ -3036,21 +3074,25 @@ void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, uint style::color semibold = (selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); style::color regular = (selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); - int32 lshift = st::msgPadding.left() + st::webPageLeft, rshift = st::msgPadding.right(), bshift = 0; - width -= lshift + rshift; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + width -= padding.left() + padding.right(); - QRect bar(rtlrect(st::msgPadding.left(), st::msgPadding.top(), st::webPageBar, _height - bshift, _width)); + QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, _height - tshift - bshift, _width)); p.fillRect(bar, barfg); - int32 tshift = st::msgPadding.top(); if (_titleLines) { p.setPen(st::black); int32 endskip = 0; if (_title.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _title.drawLeftElided(p, lshift, tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); + _title.drawLeftElided(p, padding.left(), tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); tshift += _titleLines * _lineHeight; } if (_descriptionLines) { @@ -3059,13 +3101,15 @@ void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, uint if (_description.hasSkipBlock()) { endskip = _parent->skipBlockWidth(); } - _description.drawLeftElided(p, lshift, tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); + _description.drawLeftElided(p, padding.left(), tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); tshift += _descriptionLines * _lineHeight; } if (_attach) { - if (tshift) tshift += st::webPagePhotoSkip; + auto attachAtTop = !_titleLines && !_descriptionLines; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; - int32 attachLeft = lshift - bubble.left(), attachTop = tshift - bubble.top(); + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; @@ -3082,17 +3126,22 @@ HistoryTextState HistoryGame::getState(int x, int y, HistoryStateRequest request if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; int32 width = _width, height = _height; - int32 lshift = st::msgPadding.left() + st::webPageLeft, rshift = st::msgPadding.right(), bshift = 0; - width -= lshift + rshift; QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + width -= padding.left() + padding.right(); bool inThumb = false; - int tshift = st::msgPadding.top(), symbolAdd = 0; + int symbolAdd = 0; if (_titleLines) { if (y >= tshift && y < tshift + _titleLines * _lineHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; - result = _title.getStateElidedLeft(x - lshift, y - tshift, width, _width, titleRequest); + result = _title.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, titleRequest); } else if (y >= tshift + _titleLines * _lineHeight) { symbolAdd += _title.length(); } @@ -3102,7 +3151,7 @@ HistoryTextState HistoryGame::getState(int x, int y, HistoryStateRequest request if (y >= tshift && y < tshift + _descriptionLines * _lineHeight) { Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; - result = _description.getStateElidedLeft(x - lshift, y - tshift, width, _width, descriptionRequest); + result = _description.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, descriptionRequest); } else if (y >= tshift + _descriptionLines * _lineHeight) { symbolAdd += _description.length(); } @@ -3111,9 +3160,14 @@ HistoryTextState HistoryGame::getState(int x, int y, HistoryStateRequest request if (inThumb) { result.link = _openl; } else if (_attach) { - if (tshift) tshift += st::webPagePhotoSkip; + auto attachAtTop = !_titleLines && !_descriptionLines; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; - if (x >= lshift && x < lshift + width && y >= tshift && y < _height) { + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); + + if (x >= attachLeft && x < attachLeft + _attach->currentWidth() && y >= tshift && y < _height - bshift) { result.link = _openl; } } @@ -3177,6 +3231,28 @@ ImagePtr HistoryGame::replyPreview() { return _attach ? _attach->replyPreview() : (_data->photo ? _data->photo->makeReplyPreview() : ImagePtr()); } +QMargins HistoryGame::inBubblePadding() const { + auto lshift = st::msgPadding.left() + st::webPageLeft; + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.left() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +int HistoryGame::bottomInfoPadding() const { + if (!isBubbleBottom()) return 0; + + auto result = st::msgDateFont->height; + + // we use padding greater than st::msgPadding.bottom() in the + // bottom of the bubble so that the left line looks pretty. + // but if we have bottom skip because of the info display + // we don't need that additional padding so we replace it + // back with st::msgPadding.bottom() instead of left(). + result += st::msgPadding.bottom() - st::msgPadding.left(); + return result; +} + HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent) , _data(App::location(coords)) , _title(st::msgMinWidth) @@ -3220,8 +3296,8 @@ void HistoryLocation::initDimensions() { } _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); if (!_title.isEmpty() || !_description.isEmpty()) { - _minh += st::webPagePhotoSkip; - if (!_parent->Has() && !_parent->Has()) { + _minh += st::mediaInBubbleSkip; + if (isBubbleTop()) { _minh += st::msgPadding.top(); } } @@ -3260,8 +3336,8 @@ int HistoryLocation::resizeGetHeight(int width) { _height += qMin(_description.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()), st::webPageDescriptionFont->height * 3); } if (!_title.isEmpty() || !_description.isEmpty()) { - _height += st::webPagePhotoSkip; - if (!_parent->Has() && !_parent->Has()) { + _height += st::mediaInBubbleSkip; + if (isBubbleTop()) { _height += st::msgPadding.top(); } } @@ -3281,7 +3357,7 @@ void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, skipy = st::mediaPadding.top(); if (!_title.isEmpty() || !_description.isEmpty()) { - if (!_parent->Has() && !_parent->Has()) { + if (isBubbleTop()) { skipy += st::msgPadding.top(); } } @@ -3299,7 +3375,7 @@ void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection, skipy += qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height); } if (!_title.isEmpty() || !_description.isEmpty()) { - skipy += st::webPagePhotoSkip; + skipy += st::mediaInBubbleSkip; } height -= skipy + st::mediaPadding.bottom(); } else { @@ -3347,7 +3423,7 @@ HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest req skipy = st::mediaPadding.top(); if (!_title.isEmpty() || !_description.isEmpty()) { - if (!_parent->Has() && !_parent->Has()) { + if (isBubbleTop()) { skipy += st::msgPadding.top(); } } @@ -3375,7 +3451,7 @@ HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest req skipy += descriptionh; } if (!_title.isEmpty() || !_description.isEmpty()) { - skipy += st::webPagePhotoSkip; + skipy += st::mediaInBubbleSkip; } height -= skipy + st::mediaPadding.bottom(); } diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h new file mode 100644 index 0000000000..18c18dd604 --- /dev/null +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -0,0 +1,906 @@ +/* +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 + +#include "ui/effects/radial_animation.h" + +void historyInitMedia(); + +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; + + Ui::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 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); + } + QMargins inBubblePadding() const; + int bottomInfoPadding() const; + + 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); + } + QMargins inBubblePadding() const; + int bottomInfoPadding() const; + + 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; + +}; + +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; + +}; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index b0611c9142..5c4d538c9a 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "history/history_location_manager.h" #include "history/history_service_layout.h" +#include "history/history_media_types.h" #include "styles/style_dialogs.h" namespace { @@ -476,6 +477,39 @@ void HistoryMessage::createComponentsHelper(MTPDmessage::Flags flags, MsgId repl createComponents(config); } +void HistoryMessage::updateMediaInBubbleState() { + if (!_media) { + return; + } + + if (!drawBubble()) { + _media->setInBubbleState(MediaInBubbleState::None); + return; + } + + bool hasSomethingAbove = displayFromName() || displayForwardedFrom() || Has(); + bool hasSomethingBelow = false; + if (!emptyText()) { + if (_media->isAboveMessage()) { + hasSomethingBelow = true; + } else { + hasSomethingAbove = true; + } + } + auto computeState = [hasSomethingAbove, hasSomethingBelow] { + if (hasSomethingAbove) { + if (hasSomethingBelow) { + return MediaInBubbleState::Middle; + } + return MediaInBubbleState::Bottom; + } else if (hasSomethingBelow) { + return MediaInBubbleState::Top; + } + return MediaInBubbleState::None; + }; + _media->setInBubbleState(computeState()); +} + bool HistoryMessage::displayEditedBadge(bool hasViaBot) const { if (!(_flags & MTPDmessage::Flag::f_edit_date)) { return false; @@ -658,6 +692,8 @@ void HistoryMessage::initDimensions() { if (reply) { reply->updateName(); } + + updateMediaInBubbleState(); if (drawBubble()) { auto fwd = Get(); auto via = Get(); @@ -665,9 +701,11 @@ void HistoryMessage::initDimensions() { fwd->create(via); } + auto mediaDisplayed = false; if (_media) { + mediaDisplayed = _media->isDisplayed(); _media->initDimensions(); - if (_media->isDisplayed() && !_media->isAboveMessage()) { + if (mediaDisplayed && _media->isBubbleBottom()) { if (_text.hasSkipBlock()) { _text.removeSkipBlock(); _textWidth = -1; @@ -681,19 +719,21 @@ void HistoryMessage::initDimensions() { } _maxw = plainMaxWidth(); - if (emptyText()) { - _minh = 0; - } else { - _minh = st::msgPadding.top() + _text.minHeight() + st::msgPadding.bottom(); - } - if (_media && _media->isDisplayed()) { - int32 maxw = _media->maxWidth(); + _minh = emptyText() ? 0 : _text.minHeight(); + if (mediaDisplayed) { + if (!_media->isBubbleTop()) { + _minh += st::msgPadding.top() + st::mediaInBubbleSkip; + } + if (!_media->isBubbleBottom()) { + _minh += st::msgPadding.bottom() + st::mediaInBubbleSkip; + } + auto maxw = _media->maxWidth(); if (maxw > _maxw) _maxw = maxw; _minh += _media->minHeight(); - } - if (!_media) { + } else { + _minh += st::msgPadding.top() + st::msgPadding.bottom(); if (displayFromName()) { - int32 namew = st::msgPadding.left() + author()->nameText.maxWidth() + st::msgPadding.right(); + auto namew = st::msgPadding.left() + author()->nameText.maxWidth() + st::msgPadding.right(); if (via && !fwd) { namew += st::msgServiceFont->spacew + via->_maxWidth; } @@ -704,7 +744,7 @@ void HistoryMessage::initDimensions() { } } if (fwd) { - int32 _namew = st::msgPadding.left() + fwd->_text.maxWidth() + st::msgPadding.right(); + auto _namew = st::msgPadding.left() + fwd->_text.maxWidth() + st::msgPadding.right(); if (via) { _namew += st::msgServiceFont->spacew + via->_maxWidth; } @@ -1029,19 +1069,19 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width int32 infoRight = right, infoBottom = bottom; switch (type) { case InfoDisplayDefault: - infoRight -= st::msgPadding.right() - st::msgDateDelta.x(); - infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y(); - p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); + infoRight -= st::msgPadding.right() - st::msgDateDelta.x(); + infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y(); + p.setPen(selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg)); break; case InfoDisplayOverImage: - infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); - infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); - p.setPen(st::msgDateImgColor); + infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); + infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); + p.setPen(st::msgDateImgColor); break; case InfoDisplayOverBackground: - infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); - infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); - p.setPen(st::msgServiceColor); + infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); + infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); + p.setPen(st::msgServiceColor); break; } @@ -1152,9 +1192,6 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, u int dateh = 0, unreadbarh = 0; if (auto date = Get()) { dateh = date->height(); - //if (r.intersects(QRect(0, 0, _history->width, dateh))) { - // date->paint(p, 0, _history->width); - //} } if (auto unreadbar = Get()) { unreadbarh = unreadbar->height(); @@ -1197,7 +1234,8 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, u fromNameUpdated(width); } - int32 top = marginTop(); + auto mediaDisplayed = _media && _media->isDisplayed(); + auto top = marginTop(); QRect r(left, top, width, height - top - marginBottom()); style::color bg(selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg)); @@ -1206,17 +1244,23 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, u App::roundRect(p, r, bg, cors, &sh); QRect trect(r.marginsAdded(-st::msgPadding)); - paintFromName(p, trect, selected); - paintForwardedInfo(p, trect, selected); - paintReplyInfo(p, trect, selected); - paintViaBotIdInfo(p, trect, selected); - + if (mediaDisplayed && _media->isBubbleTop()) { + trect.setY(trect.y() - st::msgPadding.top()); + } else { + paintFromName(p, trect, selected); + paintForwardedInfo(p, trect, selected); + paintReplyInfo(p, trect, selected); + paintViaBotIdInfo(p, trect, selected); + } + if (mediaDisplayed && _media->isBubbleBottom()) { + trect.setHeight(trect.height() + st::msgPadding.bottom()); + } auto needDrawInfo = true; - if (_media && _media->isDisplayed()) { + if (mediaDisplayed) { auto mediaAboveText = _media->isAboveMessage(); auto mediaHeight = _media->height(); auto mediaLeft = trect.x() - st::msgPadding.left(); - auto mediaTop = mediaAboveText ? (trect.y() - st::msgPadding.top()) : (r.y() + r.height() - mediaHeight); + auto mediaTop = mediaAboveText ? trect.y() : (trect.y() + trect.height() - mediaHeight); if (!mediaAboveText) { paintText(p, trect, selection); } @@ -1361,46 +1405,50 @@ int HistoryMessage::performResizeGetHeight(int width) { auto reply = Get(); auto via = Get(); - bool media = (_media && _media->isDisplayed()); + auto mediaDisplayed = false; + auto mediaInBubbleState = MediaInBubbleState::None; + if (_media) { + mediaDisplayed = _media->isDisplayed(); + mediaInBubbleState = _media->inBubbleState(); + } if (width >= _maxw) { _height = _minh; - if (media) _media->resizeGetHeight(_maxw); + if (mediaDisplayed) _media->resizeGetHeight(_maxw); } else { if (emptyText()) { _height = 0; } else { - int32 textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1); + auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1); if (textWidth != _textWidth) { textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); _textWidth = textWidth; _textHeight = _text.countHeight(textWidth); textstyleRestore(); } - _height = st::msgPadding.top() + _textHeight + st::msgPadding.bottom(); + _height = _textHeight; + } + if (mediaDisplayed) { + if (!_media->isBubbleTop()) { + _height += st::msgPadding.top() + st::mediaInBubbleSkip; + } + if (!_media->isBubbleBottom()) { + _height += st::msgPadding.bottom() + st::mediaInBubbleSkip; + } + _height += _media->resizeGetHeight(width); + } else { + _height += st::msgPadding.top() + st::msgPadding.bottom(); } - if (media) _height += _media->resizeGetHeight(width); } - auto mediaTopPaddingAdded = !emptyText(); if (displayFromName()) { int32 l = 0, w = 0; countPositionAndSize(l, w); fromNameUpdated(w); - - if (!mediaTopPaddingAdded) { - _height += st::msgPadding.top() + st::mediaHeaderSkip; - mediaTopPaddingAdded = true; - } _height += st::msgNameFont->height; } else if (via && !fwd) { int32 l = 0, w = 0; countPositionAndSize(l, w); via->resize(w - st::msgPadding.left() - st::msgPadding.right()); - - if (!mediaTopPaddingAdded) { - _height += st::msgPadding.top() + st::mediaHeaderSkip; - mediaTopPaddingAdded = true; - } _height += st::msgNameFont->height; } @@ -1408,11 +1456,6 @@ int HistoryMessage::performResizeGetHeight(int width) { int32 l = 0, w = 0; countPositionAndSize(l, w); int32 fwdheight = ((fwd->_text.maxWidth() > (w - st::msgPadding.left() - st::msgPadding.right())) ? 2 : 1) * st::semiboldFont->height; - - if (!mediaTopPaddingAdded) { - _height += st::msgPadding.top() + st::mediaHeaderSkip; - mediaTopPaddingAdded = true; - } _height += fwdheight; } @@ -1420,11 +1463,6 @@ int HistoryMessage::performResizeGetHeight(int width) { int32 l = 0, w = 0; countPositionAndSize(l, w); reply->resize(w - st::msgPadding.left() - st::msgPadding.right()); - - if (!mediaTopPaddingAdded) { - _height += st::msgPadding.top() + st::mediaHeaderSkip; - mediaTopPaddingAdded = true; - } _height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); } } else if (_media) { @@ -1465,12 +1503,12 @@ bool HistoryMessage::pointInTime(int32 right, int32 bottom, int x, int y, InfoDi int32 infoRight = right, infoBottom = bottom; switch (type) { case InfoDisplayDefault: - infoRight -= st::msgPadding.right() - st::msgDateDelta.x(); - infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y(); + infoRight -= st::msgPadding.right() - st::msgDateDelta.x(); + infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y(); break; case InfoDisplayOverImage: - infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); - infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); + infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x(); + infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y(); break; } int32 dateX = infoRight - HistoryMessage::infoWidth() + HistoryMessage::timeLeft(); @@ -1493,85 +1531,47 @@ HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest requ } if (drawBubble()) { - auto fwd = Get(); - auto via = Get(); - auto reply = Get(); - - int top = marginTop(); + auto mediaDisplayed = _media && _media->isDisplayed(); + auto top = marginTop(); QRect r(left, top, width, height - top - marginBottom()); QRect trect(r.marginsAdded(-st::msgPadding)); - if (displayFromName()) { - if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) { - if (x >= trect.left() && x < trect.left() + trect.width() && x < trect.left() + author()->nameText.maxWidth()) { - result.link = author()->openLink(); - return result; - } - if (via && !fwd && x >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && x < trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) { - result.link = via->_lnk; - return result; - } - } - trect.setTop(trect.top() + st::msgNameFont->height); + if (mediaDisplayed && _media->isBubbleTop()) { + trect.setY(trect.y() - st::msgPadding.top()); + } else { + if (getStateFromName(x, y, trect, &result)) return result; + if (getStateForwardedInfo(x, y, trect, &result, request)) return result; + if (getStateReplyInfo(x, y, trect, &result)) return result; + if (getStateViaBotIdInfo(x, y, trect, &result)) return result; } - if (displayForwardedFrom()) { - int32 fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; - if (y >= trect.top() && y < trect.top() + fwdheight) { - bool breakEverywhere = (fwd->_text.countHeight(trect.width()) > 2 * st::semiboldFont->height); - auto textRequest = request.forText(); - if (breakEverywhere) { - textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; - } - textstyleSet(&st::inFwdTextStyle); - result = fwd->_text.getState(x - trect.left(), y - trect.top(), trect.width(), textRequest); - textstyleRestore(); - result.symbol = 0; - result.afterSymbol = false; - if (breakEverywhere) { - result.cursor = HistoryInForwardedCursorState; - } else { - result.cursor = HistoryDefaultCursorState; - } - return result; - } - trect.setTop(trect.top() + fwdheight); - } - if (reply) { - int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); - if (y >= trect.top() && y < trect.top() + h) { - if (reply->replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.left() + trect.width()) { - result.link = reply->replyToLink(); - } - return result; - } - trect.setTop(trect.top() + h); - } - if (via && !displayFromName() && !displayForwardedFrom()) { - if (x >= trect.left() && y >= trect.top() && y < trect.top() + st::msgNameFont->height && x < trect.left() + via->_width) { - result.link = via->_lnk; - return result; - } - trect.setTop(trect.top() + st::msgNameFont->height); - } - - bool inDate = false, mediaDisplayed = _media && _media->isDisplayed(); - if (!mediaDisplayed || !_media->customInfoLayout()) { - inDate = HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault); + if (mediaDisplayed && _media->isBubbleBottom()) { + trect.setHeight(trect.height() + st::msgPadding.bottom()); } + auto needDateCheck = true; if (mediaDisplayed) { - trect.setBottom(trect.bottom() - _media->height()); - if (y >= r.bottom() - _media->height()) { - result = _media->getState(x - r.left(), y - (r.bottom() - _media->height()), request); + auto mediaAboveText = _media->isAboveMessage(); + auto mediaHeight = _media->height(); + auto mediaLeft = trect.x() - st::msgPadding.left(); + auto mediaTop = mediaAboveText ? trect.y() : (trect.y() + trect.height() - mediaHeight); + + if (y >= mediaTop && y < mediaTop + mediaHeight) { + result = _media->getState(x - mediaLeft, y - mediaTop, request); result.symbol += _text.length(); + } else { + if (mediaAboveText) { + trect.setY(trect.y() + mediaHeight); + } + getStateText(x, y, trect, &result, request); } + + needDateCheck = !_media->customInfoLayout(); + } else { + getStateText(x, y, trect, &result, request); } - if (!mediaDisplayed || (y < r.bottom() - _media->height())) { - textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); - result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), request.forText()); - textstyleRestore(); - } - if (inDate) { - result.cursor = HistoryInDateCursorState; + if (needDateCheck) { + if (HistoryMessage::pointInTime(r.x() + r.width(), r.y() + r.height(), x, y, InfoDisplayDefault)) { + result.cursor = HistoryInDateCursorState; + } } } else if (_media) { result = _media->getState(x - left, y - marginTop(), request); @@ -1589,6 +1589,89 @@ HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest requ return result; } +bool HistoryMessage::getStateFromName(int x, int y, QRect &trect, HistoryTextState *outResult) const { + if (displayFromName()) { + if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) { + if (x >= trect.left() && x < trect.left() + trect.width() && x < trect.left() + author()->nameText.maxWidth()) { + outResult->link = author()->openLink(); + return true; + } + auto fwd = Get(); + auto via = Get(); + if (via && !fwd && x >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && x < trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) { + outResult->link = via->_lnk; + return true; + } + } + trect.setTop(trect.top() + st::msgNameFont->height); + } + return false; +} + +bool HistoryMessage::getStateForwardedInfo(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const { + if (displayForwardedFrom()) { + auto fwd = Get(); + int32 fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; + if (y >= trect.top() && y < trect.top() + fwdheight) { + bool breakEverywhere = (fwd->_text.countHeight(trect.width()) > 2 * st::semiboldFont->height); + auto textRequest = request.forText(); + if (breakEverywhere) { + textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere; + } + textstyleSet(&st::inFwdTextStyle); + *outResult = fwd->_text.getState(x - trect.left(), y - trect.top(), trect.width(), textRequest); + textstyleRestore(); + outResult->symbol = 0; + outResult->afterSymbol = false; + if (breakEverywhere) { + outResult->cursor = HistoryInForwardedCursorState; + } else { + outResult->cursor = HistoryDefaultCursorState; + } + return true; + } + trect.setTop(trect.top() + fwdheight); + } + return false; +} + +bool HistoryMessage::getStateReplyInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const { + if (auto reply = Get()) { + int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + if (y >= trect.top() && y < trect.top() + h) { + if (reply->replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.left() + trect.width()) { + outResult->link = reply->replyToLink(); + } + return true; + } + trect.setTop(trect.top() + h); + } + return false; +} + +bool HistoryMessage::getStateViaBotIdInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const { + if (!displayFromName() && !Has()) { + if (auto via = Get()) { + if (x >= trect.left() && y >= trect.top() && y < trect.top() + st::msgNameFont->height && x < trect.left() + via->_width) { + outResult->link = via->_lnk; + return true; + } + trect.setTop(trect.top() + st::msgNameFont->height); + } + } + return false; +} + +bool HistoryMessage::getStateText(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const { + if (trect.contains(x, y)) { + textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle)); + *outResult = _text.getState(x - trect.x(), y - trect.y(), trect.width(), request.forText()); + textstyleRestore(); + return true; + } + return false; +} + TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelectType type) const { if (!_media || selection.to <= _text.length()) { return _text.adjustSelection(selection, type); diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 7755d546af..7c3a1a14f1 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -174,7 +174,7 @@ private: void applyEditionToEmpty(); bool displayForwardedFrom() const { - if (const HistoryMessageForwarded *fwd = Get()) { + if (auto fwd = Get()) { return Has() || !_media || !_media->isDisplayed() || fwd->_authorOriginal->isChannel() || !_media->hideForwardedFrom(); } return false; @@ -182,12 +182,16 @@ private: void paintFromName(Painter &p, QRect &trect, bool selected) const; void paintForwardedInfo(Painter &p, QRect &trect, bool selected) const; void paintReplyInfo(Painter &p, QRect &trect, bool selected) const; - // this method draws "via @bot" if it is not painted in forwarded info or in from name void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const; - void paintText(Painter &p, QRect &trect, TextSelection selection) const; + bool getStateFromName(int x, int y, QRect &trect, HistoryTextState *outResult) const; + bool getStateForwardedInfo(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const; + bool getStateReplyInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const; + bool getStateViaBotIdInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const; + bool getStateText(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const; + void setMedia(const MTPMessageMedia *media); void setReplyMarkup(const MTPReplyMarkup *markup); @@ -223,6 +227,8 @@ private: }; + void updateMediaInBubbleState(); + }; inline MTPDmessage::Flags newMessageFlags(PeerData *p) { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index b4dc8eb68c..9845a07180 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -33,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "inline_bots/inline_bot_result.h" #include "data/data_drafts.h" #include "history/history_service_layout.h" +#include "history/history_media_types.h" #include "profile/profile_members_widget.h" #include "core/click_handler_types.h" #include "stickers/emoji_pan.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index 85a8ccc081..d151cb86b7 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "inline_bots/inline_bot_layout_item.h" +#include "ui/effects/radial_animation.h" #include "ui/text/text.h" namespace InlineBots { @@ -113,7 +114,7 @@ private: } bool over; FloatAnimation _a_over; - RadialAnimation radial; + Ui::RadialAnimation radial; }; mutable AnimationData *_animation = nullptr; mutable FloatAnimation _a_deleteOver; @@ -275,7 +276,7 @@ private: anim::fvalue a_thumbOver; Animation _a_thumbOver; - RadialAnimation radial; + Ui::RadialAnimation radial; }; mutable std_::unique_ptr _animation; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 8e4ea3e3c1..33bdb89243 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/view/media_clip_controller.h" #include "styles/style_mediaview.h" #include "media/media_audio.h" +#include "history/history_media_types.h" namespace { diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index ff1e85b6ad..bc18fcd1f6 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "dropdown.h" +#include "ui/effects/radial_animation.h" namespace Media { namespace Clip { @@ -237,7 +238,7 @@ private: LinkButton _docDownload, _docSaveAs, _docCancel; QRect _photoRadialRect; - RadialAnimation _radial; + Ui::RadialAnimation _radial; History *_migrated = nullptr; History *_history = nullptr; // if conversation photos or files overview diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 286280dd9d..a68bf1fc48 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -654,7 +654,7 @@ inputBotInlineMessageGame#3c00f8aa reply_markup:ReplyMarkup = InputBotInlineMess inputBotInlineResult#2cbbe15a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb_url:flags.4?string content_url:flags.5?string content_type:flags.5?string w:flags.6?int h:flags.6?int duration:flags.7?int send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult; -inputBotInlineResultGame#efff34f9 flags:# id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult; +inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult; botInlineMessageMediaAuto#a74b15b flags:# caption:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; @@ -725,7 +725,7 @@ maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords; inputStickeredMediaPhoto#4a992157 id:InputPhoto = InputStickeredMedia; inputStickeredMediaDocument#438865b id:InputDocument = InputStickeredMedia; -game#b351c590 flags:# id:long access_hash:long short_name:string title:string description:string url:string photo:Photo document:flags.0?Document = Game; +game#bdf9653b flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document = Game; inputGameID#32c3e77 id:long access_hash:long = InputGame; inputGameShortName#c331e80a bot_id:InputUser short_name:string = InputGame; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index a716a5f309..4d9b5e892f 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -5471,10 +5471,9 @@ void _serialize_inputBotInlineResultGame(MTPStringLogger &to, int32 stage, int32 to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" short_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" send_message: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" short_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" send_message: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -6064,9 +6063,8 @@ void _serialize_game(MTPStringLogger &to, int32 stage, int32 lev, Types &types, case 3: to.add(" short_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 4: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 5: to.add(" description: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 6: to.add(" url: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 7: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 8: to.add(" document: "); ++stages.back(); if (flag & MTPDgame::Flag::f_document) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 6: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 7: to.add(" document: "); ++stages.back(); if (flag & MTPDgame::Flag::f_document) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index 5767a2ad8c..66f7527d57 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -478,7 +478,7 @@ enum { mtpc_inputBotInlineResult = 0x2cbbe15a, mtpc_inputBotInlineResultPhoto = 0xa8d864a7, mtpc_inputBotInlineResultDocument = 0xfff8fdc4, - mtpc_inputBotInlineResultGame = 0xefff34f9, + mtpc_inputBotInlineResultGame = 0x4fa417f2, mtpc_botInlineMessageMediaAuto = 0xa74b15b, mtpc_botInlineMessageText = 0x8c7f65e2, mtpc_botInlineMessageMediaGeo = 0x3a8fd8b8, @@ -524,7 +524,7 @@ enum { mtpc_maskCoords = 0xaed6dbb2, mtpc_inputStickeredMediaPhoto = 0x4a992157, mtpc_inputStickeredMediaDocument = 0x438865b, - mtpc_game = 0xb351c590, + mtpc_game = 0xbdf9653b, mtpc_inputGameID = 0x32c3e77, mtpc_inputGameShortName = 0xc331e80a, mtpc_highScore = 0x58fffcd0, @@ -14738,18 +14738,11 @@ public: class MTPDinputBotInlineResultGame : public mtpDataImpl { public: - enum class Flag : int32 { - MAX_FIELD = (1 << 0), - }; - Q_DECLARE_FLAGS(Flags, Flag); - friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } - MTPDinputBotInlineResultGame() { } - MTPDinputBotInlineResultGame(const MTPflags &_flags, const MTPstring &_id, const MTPstring &_short_name, const MTPInputBotInlineMessage &_send_message) : vflags(_flags), vid(_id), vshort_name(_short_name), vsend_message(_send_message) { + MTPDinputBotInlineResultGame(const MTPstring &_id, const MTPstring &_short_name, const MTPInputBotInlineMessage &_send_message) : vid(_id), vshort_name(_short_name), vsend_message(_send_message) { } - MTPflags vflags; MTPstring vid; MTPstring vshort_name; MTPInputBotInlineMessage vsend_message; @@ -15322,7 +15315,7 @@ public: MTPDgame() { } - MTPDgame(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, const MTPstring &_short_name, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPPhoto &_photo, const MTPDocument &_document) : vflags(_flags), vid(_id), vaccess_hash(_access_hash), vshort_name(_short_name), vtitle(_title), vdescription(_description), vurl(_url), vphoto(_photo), vdocument(_document) { + MTPDgame(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, const MTPstring &_short_name, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo, const MTPDocument &_document) : vflags(_flags), vid(_id), vaccess_hash(_access_hash), vshort_name(_short_name), vtitle(_title), vdescription(_description), vphoto(_photo), vdocument(_document) { } MTPflags vflags; @@ -15331,7 +15324,6 @@ public: MTPstring vshort_name; MTPstring vtitle; MTPstring vdescription; - MTPstring vurl; MTPPhoto vphoto; MTPDocument vdocument; }; @@ -25161,8 +25153,8 @@ public: inline static MTPinputBotInlineResult new_inputBotInlineResultDocument(const MTPflags &_flags, const MTPstring &_id, const MTPstring &_type, const MTPstring &_title, const MTPstring &_description, const MTPInputDocument &_document, const MTPInputBotInlineMessage &_send_message) { return MTPinputBotInlineResult(new MTPDinputBotInlineResultDocument(_flags, _id, _type, _title, _description, _document, _send_message)); } - inline static MTPinputBotInlineResult new_inputBotInlineResultGame(const MTPflags &_flags, const MTPstring &_id, const MTPstring &_short_name, const MTPInputBotInlineMessage &_send_message) { - return MTPinputBotInlineResult(new MTPDinputBotInlineResultGame(_flags, _id, _short_name, _send_message)); + inline static MTPinputBotInlineResult new_inputBotInlineResultGame(const MTPstring &_id, const MTPstring &_short_name, const MTPInputBotInlineMessage &_send_message) { + return MTPinputBotInlineResult(new MTPDinputBotInlineResultGame(_id, _short_name, _send_message)); } inline static MTPbotInlineMessage new_botInlineMessageMediaAuto(const MTPflags &_flags, const MTPstring &_caption, const MTPReplyMarkup &_reply_markup) { return MTPbotInlineMessage(new MTPDbotInlineMessageMediaAuto(_flags, _caption, _reply_markup)); @@ -25299,8 +25291,8 @@ public: inline static MTPinputStickeredMedia new_inputStickeredMediaDocument(const MTPInputDocument &_id) { return MTPinputStickeredMedia(new MTPDinputStickeredMediaDocument(_id)); } - inline static MTPgame new_game(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, const MTPstring &_short_name, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPPhoto &_photo, const MTPDocument &_document) { - return MTPgame(new MTPDgame(_flags, _id, _access_hash, _short_name, _title, _description, _url, _photo, _document)); + inline static MTPgame new_game(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, const MTPstring &_short_name, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo, const MTPDocument &_document) { + return MTPgame(new MTPDgame(_flags, _id, _access_hash, _short_name, _title, _description, _photo, _document)); } inline static MTPinputGame new_inputGameID(const MTPlong &_id, const MTPlong &_access_hash) { return MTPinputGame(new MTPDinputGameID(_id, _access_hash)); @@ -35831,7 +35823,7 @@ inline uint32 MTPinputBotInlineResult::innerLength() const { } case mtpc_inputBotInlineResultGame: { const MTPDinputBotInlineResultGame &v(c_inputBotInlineResultGame()); - return v.vflags.innerLength() + v.vid.innerLength() + v.vshort_name.innerLength() + v.vsend_message.innerLength(); + return v.vid.innerLength() + v.vshort_name.innerLength() + v.vsend_message.innerLength(); } } return 0; @@ -35882,7 +35874,6 @@ inline void MTPinputBotInlineResult::read(const mtpPrime *&from, const mtpPrime case mtpc_inputBotInlineResultGame: _type = cons; { if (!data) setData(new MTPDinputBotInlineResultGame()); MTPDinputBotInlineResultGame &v(_inputBotInlineResultGame()); - v.vflags.read(from, end); v.vid.read(from, end); v.vshort_name.read(from, end); v.vsend_message.read(from, end); @@ -35927,7 +35918,6 @@ inline void MTPinputBotInlineResult::write(mtpBuffer &to) const { } break; case mtpc_inputBotInlineResultGame: { const MTPDinputBotInlineResultGame &v(c_inputBotInlineResultGame()); - v.vflags.write(to); v.vid.write(to); v.vshort_name.write(to); v.vsend_message.write(to); @@ -35962,8 +35952,8 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDinputBotInlineResultDocument::Flags) inline MTPinputBotInlineResult MTP_inputBotInlineResultDocument(const MTPflags &_flags, const MTPstring &_id, const MTPstring &_type, const MTPstring &_title, const MTPstring &_description, const MTPInputDocument &_document, const MTPInputBotInlineMessage &_send_message) { return MTP::internal::TypeCreator::new_inputBotInlineResultDocument(_flags, _id, _type, _title, _description, _document, _send_message); } -inline MTPinputBotInlineResult MTP_inputBotInlineResultGame(const MTPflags &_flags, const MTPstring &_id, const MTPstring &_short_name, const MTPInputBotInlineMessage &_send_message) { - return MTP::internal::TypeCreator::new_inputBotInlineResultGame(_flags, _id, _short_name, _send_message); +inline MTPinputBotInlineResult MTP_inputBotInlineResultGame(const MTPstring &_id, const MTPstring &_short_name, const MTPInputBotInlineMessage &_send_message) { + return MTP::internal::TypeCreator::new_inputBotInlineResultGame(_id, _short_name, _send_message); } inline uint32 MTPbotInlineMessage::innerLength() const { @@ -37184,7 +37174,7 @@ inline MTPgame::MTPgame() : mtpDataOwner(new MTPDgame()) { inline uint32 MTPgame::innerLength() const { const MTPDgame &v(c_game()); - return v.vflags.innerLength() + v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vshort_name.innerLength() + v.vtitle.innerLength() + v.vdescription.innerLength() + v.vurl.innerLength() + v.vphoto.innerLength() + (v.has_document() ? v.vdocument.innerLength() : 0); + return v.vflags.innerLength() + v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vshort_name.innerLength() + v.vtitle.innerLength() + v.vdescription.innerLength() + v.vphoto.innerLength() + (v.has_document() ? v.vdocument.innerLength() : 0); } inline mtpTypeId MTPgame::type() const { return mtpc_game; @@ -37200,7 +37190,6 @@ inline void MTPgame::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId v.vshort_name.read(from, end); v.vtitle.read(from, end); v.vdescription.read(from, end); - v.vurl.read(from, end); v.vphoto.read(from, end); if (v.has_document()) { v.vdocument.read(from, end); } else { v.vdocument = MTPDocument(); } } @@ -37212,15 +37201,14 @@ inline void MTPgame::write(mtpBuffer &to) const { v.vshort_name.write(to); v.vtitle.write(to); v.vdescription.write(to); - v.vurl.write(to); v.vphoto.write(to); if (v.has_document()) v.vdocument.write(to); } inline MTPgame::MTPgame(MTPDgame *_data) : mtpDataOwner(_data) { } Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDgame::Flags) -inline MTPgame MTP_game(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, const MTPstring &_short_name, const MTPstring &_title, const MTPstring &_description, const MTPstring &_url, const MTPPhoto &_photo, const MTPDocument &_document) { - return MTP::internal::TypeCreator::new_game(_flags, _id, _access_hash, _short_name, _title, _description, _url, _photo, _document); +inline MTPgame MTP_game(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, const MTPstring &_short_name, const MTPstring &_title, const MTPstring &_description, const MTPPhoto &_photo, const MTPDocument &_document) { + return MTP::internal::TypeCreator::new_game(_flags, _id, _access_hash, _short_name, _title, _description, _photo, _document); } inline uint32 MTPinputGame::innerLength() const { diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index dad19fdabc..7bf875dfa8 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -33,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "playerwidget.h" #include "media/media_audio.h" #include "localstorage.h" +#include "history/history_media_types.h" namespace Overview { namespace Layout { @@ -91,7 +92,7 @@ void RadialProgressItem::step_radial(uint64 ms, bool timer) { void RadialProgressItem::ensureRadial() const { if (!_radial) { - _radial = new RadialAnimation(animation(const_cast(this), &RadialProgressItem::step_radial)); + _radial = new Ui::RadialAnimation(animation(const_cast(this), &RadialProgressItem::step_radial)); } } diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index ed46ee8085..0705e5e216 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "layout.h" #include "core/click_handler_types.h" +#include "ui/effects/radial_animation.h" namespace Overview { namespace Layout { @@ -130,7 +131,7 @@ protected: return false; } - mutable RadialAnimation *_radial; + mutable Ui::RadialAnimation *_radial; anim::fvalue a_iconOver; mutable Animation _a_iconOver; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index db99a6a3df..d739f05367 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -34,6 +34,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "playerwidget.h" #include "overview/overview_layout.h" +#include "history/history_media_types.h" // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index af71285ac2..fe5d6fc4da 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "localstorage.h" #include "media/media_audio.h" +#include "history/history_media_types.h" PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent) , _a_state(animation(this, &PlayerWidget::step_state)) diff --git a/Telegram/SourceFiles/settings/settings_background_widget.h b/Telegram/SourceFiles/settings/settings_background_widget.h index f7c6c34f52..4a61b69b15 100644 --- a/Telegram/SourceFiles/settings/settings_background_widget.h +++ b/Telegram/SourceFiles/settings/settings_background_widget.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "settings/settings_block_widget.h" +#include "ui/effects/radial_animation.h" #include "ui/filedialog.h" class LinkButton; @@ -61,7 +62,7 @@ private: ChildWidget _chooseFromGallery; ChildWidget _chooseFromFile; - RadialAnimation _radial; + Ui::RadialAnimation _radial; }; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 9838efac82..bb33e2d24f 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -33,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "media/media_audio.h" #include "localstorage.h" +#include "history/history_media_types.h" namespace { int peerColorIndex(const PeerId &peer) { @@ -1628,12 +1629,11 @@ WebPageData::WebPageData(const WebPageId &id, WebPageType type, const QString &u , pendingTill(pendingTill) { } -GameData::GameData(const GameId &id, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, const QString &url, PhotoData *photo, DocumentData *document) : id(id) +GameData::GameData(const GameId &id, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document) : id(id) , accessHash(accessHash) , shortName(shortName) , title(title) , description(description) -, url(url) , photo(photo) , document(document) { } diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index acabe10219..12545ed15f 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1368,7 +1368,7 @@ struct WebPageData { }; struct GameData { - GameData(const GameId &id, const uint64 &accessHash = 0, const QString &shortName = QString(), const QString &title = QString(), const QString &description = QString(), const QString &url = QString(), PhotoData *photo = nullptr, DocumentData *doc = nullptr); + GameData(const GameId &id, const uint64 &accessHash = 0, const QString &shortName = QString(), const QString &title = QString(), const QString &description = QString(), PhotoData *photo = nullptr, DocumentData *doc = nullptr); void forget() { if (document) document->forget(); @@ -1377,7 +1377,7 @@ struct GameData { GameId id; uint64 accessHash; - QString shortName, title, description, url; + QString shortName, title, description; PhotoData *photo; DocumentData *document; diff --git a/Telegram/SourceFiles/ui/effects/radial_animation.cpp b/Telegram/SourceFiles/ui/effects/radial_animation.cpp new file mode 100644 index 0000000000..144e8fbcb2 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/radial_animation.cpp @@ -0,0 +1,96 @@ +/* +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 +*/ +#include "stdafx.h" +#include "ui/effects/radial_animation.h" + +namespace Ui { + +RadialAnimation::RadialAnimation(AnimationCallbacks &&callbacks) + : a_arcEnd(0, 0) + , a_arcStart(0, FullArcLength) + , _animation(std_::move(callbacks)) { +} + +void RadialAnimation::start(float64 prg) { + _firstStart = _lastStart = _lastTime = getms(); + int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength), iprgstrict = qRound(prg * AlmostFullArcLength); + a_arcEnd = anim::ivalue(iprgstrict, iprg); + _animation.start(); +} + +void RadialAnimation::update(float64 prg, bool finished, uint64 ms) { + int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength); + if (iprg != a_arcEnd.to()) { + a_arcEnd.start(iprg); + _lastStart = _lastTime; + } + _lastTime = ms; + + float64 dt = float64(ms - _lastStart), fulldt = float64(ms - _firstStart); + _opacity = qMin(fulldt / st::radialDuration, 1.); + if (!finished) { + a_arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear); + } else if (dt >= st::radialDuration) { + a_arcEnd.update(1, anim::linear); + stop(); + } else { + float64 r = dt / st::radialDuration; + a_arcEnd.update(r, anim::linear); + _opacity *= 1 - r; + } + float64 fromstart = fulldt / st::radialPeriod; + a_arcStart.update(fromstart - std::floor(fromstart), anim::linear); +} + +void RadialAnimation::stop() { + _firstStart = _lastStart = _lastTime = 0; + a_arcEnd = anim::ivalue(0, 0); + _animation.stop(); +} + +void RadialAnimation::step(uint64 ms) { + _animation.step(ms); +} + +void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, const style::color &color) { + float64 o = p.opacity(); + p.setOpacity(o * _opacity); + + QPen pen(color->p), was(p.pen()); + pen.setWidth(thickness); + p.setPen(pen); + + int32 len = MinArcLength + a_arcEnd.current(); + int32 from = QuarterArcLength - a_arcStart.current() - len; + if (rtl()) { + from = QuarterArcLength - (from - QuarterArcLength) - len; + if (from < 0) from += FullArcLength; + } + + p.setRenderHint(QPainter::HighQualityAntialiasing); + p.drawArc(inner, from, len); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + + p.setPen(was); + p.setOpacity(o); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/radial_animation.h b/Telegram/SourceFiles/ui/effects/radial_animation.h new file mode 100644 index 0000000000..d4bcfcf402 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/radial_animation.h @@ -0,0 +1,57 @@ +/* +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 + +namespace Ui { + +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; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/text/text.cpp b/Telegram/SourceFiles/ui/text/text.cpp index 5539f84723..1ca8fd9bbf 100644 --- a/Telegram/SourceFiles/ui/text/text.cpp +++ b/Telegram/SourceFiles/ui/text/text.cpp @@ -2891,6 +2891,10 @@ TextSelection Text::adjustSelection(TextSelection selection, TextSelectType sele return { from, to }; } +bool Text::isEmpty() const { + return _blocks.empty() || _blocks[0]->type() == TextBlockTSkip; +} + template void Text::enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const { if (isEmpty() || selection.empty()) { diff --git a/Telegram/SourceFiles/ui/text/text.h b/Telegram/SourceFiles/ui/text/text.h index d88980375c..57783e7881 100644 --- a/Telegram/SourceFiles/ui/text/text.h +++ b/Telegram/SourceFiles/ui/text/text.h @@ -173,9 +173,7 @@ public: return (selection.from == 0) && (selection.to >= _text.size()); } - bool isEmpty() const { - return _text.isEmpty(); - } + bool isEmpty() const; bool isNull() const { return !_font; } diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 21b4a8cadf..e84b854f8e 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -222,8 +222,9 @@ '<(src_loc)/history/history_item.h', '<(src_loc)/history/history_location_manager.cpp', '<(src_loc)/history/history_location_manager.h', - '<(src_loc)/history/history_media.cpp', '<(src_loc)/history/history_media.h', + '<(src_loc)/history/history_media_types.cpp', + '<(src_loc)/history/history_media_types.h', '<(src_loc)/history/history_message.cpp', '<(src_loc)/history/history_message.h', '<(src_loc)/history/history_service_layout.cpp', @@ -404,6 +405,8 @@ '<(src_loc)/ui/buttons/round_button.h', '<(src_loc)/ui/effects/fade_animation.cpp', '<(src_loc)/ui/effects/fade_animation.h', + '<(src_loc)/ui/effects/radial_animation.cpp', + '<(src_loc)/ui/effects/radial_animation.h', '<(src_loc)/ui/style/style_core.cpp', '<(src_loc)/ui/style/style_core.h', '<(src_loc)/ui/style/style_core_color.cpp',