From 538ffb9727ed4da4e29233a450ac30e9d27409e6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 28 Sep 2016 13:15:03 +0300 Subject: [PATCH] Moved History[Media] classes to history_media_types module. --- Telegram/Resources/art/sprite.png | Bin 181502 -> 181494 bytes Telegram/Resources/art/sprite_200x.png | Bin 246441 -> 246426 bytes Telegram/Resources/basic.style | 3 +- Telegram/SourceFiles/app.cpp | 13 +- Telegram/SourceFiles/app.h | 5 +- Telegram/SourceFiles/boxes/photosendbox.cpp | 1 + Telegram/SourceFiles/history.cpp | 1 + Telegram/SourceFiles/history/history_item.cpp | 112 +-- Telegram/SourceFiles/history/history_item.h | 42 +- Telegram/SourceFiles/history/history_media.h | 929 +----------------- ...tory_media.cpp => history_media_types.cpp} | 422 ++++---- .../SourceFiles/history/history_media_types.h | 906 +++++++++++++++++ .../SourceFiles/history/history_message.cpp | 347 ++++--- .../SourceFiles/history/history_message.h | 12 +- Telegram/SourceFiles/historywidget.cpp | 1 + .../inline_bots/inline_bot_layout_internal.h | 5 +- Telegram/SourceFiles/mediaview.cpp | 1 + Telegram/SourceFiles/mediaview.h | 3 +- Telegram/SourceFiles/mtproto/scheme.tl | 4 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 12 +- Telegram/SourceFiles/mtproto/scheme_auto.h | 40 +- .../SourceFiles/overview/overview_layout.cpp | 3 +- .../SourceFiles/overview/overview_layout.h | 3 +- Telegram/SourceFiles/overviewwidget.cpp | 1 + Telegram/SourceFiles/playerwidget.cpp | 1 + .../settings/settings_background_widget.h | 3 +- Telegram/SourceFiles/structs.cpp | 4 +- Telegram/SourceFiles/structs.h | 4 +- .../ui/effects/radial_animation.cpp | 96 ++ .../SourceFiles/ui/effects/radial_animation.h | 57 ++ Telegram/SourceFiles/ui/text/text.cpp | 4 + Telegram/SourceFiles/ui/text/text.h | 4 +- Telegram/gyp/Telegram.gyp | 5 +- 33 files changed, 1704 insertions(+), 1340 deletions(-) rename Telegram/SourceFiles/history/{history_media.cpp => history_media_types.cpp} (90%) create mode 100644 Telegram/SourceFiles/history/history_media_types.h create mode 100644 Telegram/SourceFiles/ui/effects/radial_animation.cpp create mode 100644 Telegram/SourceFiles/ui/effects/radial_animation.h diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png index bb18b32eaa1586bd26aa1c5584e9d31476880fce..179237f54a2c47dff8de5e849551154b4be2986a 100644 GIT binary patch delta 14860 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 Pz#n;OWvTMF#zFrJ-3{n5 delta 14867 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¿$32G$tki3y1#A6H3K&a)`-|8 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 F{{fB}9kl=e diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 55c6121ca..e44c89973 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 fcdfe3dcf..b7758130e 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 a1e11bf73..466fec396 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 9bc20e7ba..ff3c66b18 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 be1e0bdae..56b7b974e 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 89bd45da8..6b71261e3 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 fb82f9193..008eb9b13 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 a43301f23..c60e40748 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 991d67241..5b1d7bcc7 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 000000000..18c18dd60 --- /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 b0611c914..5c4d538c9 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 7755d546a..7c3a1a14f 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 b4dc8eb68..9845a0718 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 85a8ccc08..d151cb86b 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 8e4ea3e3c..33bdb8924 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 ff1e85b6a..bc18fcd1f 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 286280dd9..a68bf1fc4 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 a716a5f30..4d9b5e892 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 5767a2ad8..66f7527d5 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 dad19fdab..7bf875dfa 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 ed46ee808..0705e5e21 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 db99a6a3d..d739f0536 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 af71285ac..fe5d6fc4d 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 f7c6c34f5..4a61b69b1 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 9838efac8..bb33e2d24 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 acabe1021..12545ed15 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 000000000..144e8fbcb --- /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 000000000..d4bcfcf40 --- /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 5539f8472..1ca8fd9bb 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 d88980375..57783e788 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 21b4a8cad..e84b854f8 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',