From cedf8a65e765bd51cab41bc00b58119b7a9353b6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 22 Jun 2017 18:11:41 +0300 Subject: [PATCH] Better channel log entry layout inside messages. Also move HistoryService class to a separate module. --- Telegram/SourceFiles/app.h | 2 - Telegram/SourceFiles/facades.cpp | 1 + Telegram/SourceFiles/history.cpp | 8 +- Telegram/SourceFiles/history.h | 2 +- .../history/history_admin_log_inner.cpp | 27 +- .../history/history_admin_log_inner.h | 4 +- .../history/history_admin_log_item.cpp | 2 + .../history/history_inner_widget.cpp | 13 +- .../history/history_inner_widget.h | 6 +- Telegram/SourceFiles/history/history_item.cpp | 9 + Telegram/SourceFiles/history/history_item.h | 8 +- .../history/history_media_types.cpp | 70 +- .../SourceFiles/history/history_media_types.h | 58 +- .../SourceFiles/history/history_message.cpp | 847 +++--------------- .../SourceFiles/history/history_message.h | 193 +--- .../SourceFiles/history/history_service.cpp | 696 ++++++++++++++ .../SourceFiles/history/history_service.h | 166 ++++ .../history/history_service_layout.cpp | 2 + .../history/history_service_layout.h | 2 + Telegram/SourceFiles/historywidget.cpp | 13 +- Telegram/SourceFiles/mainwidget.cpp | 4 +- .../media/player/media_player_float.cpp | 1 + .../media/player/media_player_instance.cpp | 1 + .../media/player/media_player_list.cpp | 1 + Telegram/SourceFiles/mediaview.cpp | 5 +- Telegram/SourceFiles/messenger.cpp | 2 +- Telegram/SourceFiles/overviewwidget.cpp | 1 + Telegram/gyp/telegram_sources.txt | 2 + 28 files changed, 1134 insertions(+), 1012 deletions(-) create mode 100644 Telegram/SourceFiles/history/history_service.cpp create mode 100644 Telegram/SourceFiles/history/history_service.h diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index f3aeeee83..a4266f441 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -23,8 +23,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "core/basic_types.h" #include "history.h" #include "history/history_item.h" -#include "history/history_media.h" -#include "history/history_message.h" #include "layout.h" class Messenger; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 2215999ad..ad5754db6 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -31,6 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "lang/lang_keys.h" #include "base/observer.h" #include "base/task_queue.h" +#include "history/history_media.h" Q_DECLARE_METATYPE(ClickHandlerPtr); Q_DECLARE_METATYPE(Qt::MouseButton); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 0e408e160..c4bbfb5d4 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -20,7 +20,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "history.h" +#include "history/history_message.h" #include "history/history_media_types.h" +#include "history/history_service.h" #include "dialogs/dialogs_indexed_list.h" #include "styles/style_dialogs.h" #include "data/data_drafts.h" @@ -63,9 +65,9 @@ HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage } // namespace -void historyInit() { - historyInitMessages(); - historyInitMedia(); +void HistoryInit() { + HistoryInitMessages(); + HistoryInitMedia(); } History::History(const PeerId &peerId) diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index e5d42a6b7..adaf38932 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -25,7 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/effects/send_action_animations.h" #include "base/observer.h" -void historyInit(); +void HistoryInit(); class HistoryItem; using SelectedItemSet = QMap>; diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/history_admin_log_inner.cpp index e1f8f9ef5..338f177ac 100644 --- a/Telegram/SourceFiles/history/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/history_admin_log_inner.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_history.h" #include "history/history_media_types.h" +#include "history/history_message.h" #include "history/history_service_layout.h" #include "history/history_admin_log_section.h" #include "mainwindow.h" @@ -496,7 +497,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } p.translate(0, -top); - enumerateUserpics([&p, &clip](HistoryMessage *message, int userpicTop) { + enumerateUserpics([&p, &clip](gsl::not_null message, int userpicTop) { // stop the enumeration if the userpic is below the painted rect if (userpicTop >= clip.top() + clip.height()) { return false; @@ -511,7 +512,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.); - enumerateDates([&p, &clip, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](HistoryItem *item, int itemtop, int dateTop) { + enumerateDates([&p, &clip, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](gsl::not_null item, int itemtop, int dateTop) { // stop the enumeration if the date is above the painted rect if (dateTop + dateHeight <= clip.top()) { return false; @@ -664,16 +665,12 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt if (uponSelected) { _mouseAction = MouseAction::PrepareDrag; // start text drag } else if (!_pressWasInactive) { - if (dynamic_cast(App::pressedItem()->getMedia())) { - _mouseAction = MouseAction::PrepareDrag; // start sticker drag or by-date drag - } else { - if (dragState.afterSymbol) ++_mouseTextSymbol; - auto selection = TextSelection { _mouseTextSymbol, _mouseTextSymbol }; - repaintItem(std::exchange(_selectedItem, _mouseActionItem)); - _selectedText = selection; - _mouseAction = MouseAction::Selecting; - repaintItem(_mouseActionItem); - } + if (dragState.afterSymbol) ++_mouseTextSymbol; + auto selection = TextSelection { _mouseTextSymbol, _mouseTextSymbol }; + repaintItem(std::exchange(_selectedItem, _mouseActionItem)); + _selectedText = selection; + _mouseAction = MouseAction::Selecting; + repaintItem(_mouseActionItem); } } } @@ -784,9 +781,9 @@ void InnerWidget::updateSelected() { dragState = item->getState(itemPoint, request); lnkhost = item; if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) { - if (auto msg = item->toHistoryMessage()) { - if (msg->hasFromPhoto()) { - enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool { + if (auto message = item->toHistoryMessage()) { + if (message->hasFromPhoto()) { + enumerateUserpics([&dragState, &lnkhost, &point](gsl::not_null message, int userpicTop) -> bool { // stop enumeration if the userpic is below our point if (userpicTop > point.y()) { return false; diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.h b/Telegram/SourceFiles/history/history_admin_log_inner.h index dc074ff29..7aa1b18c2 100644 --- a/Telegram/SourceFiles/history/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/history_admin_log_inner.h @@ -152,7 +152,7 @@ private: // This function finds all userpics on the left that are displayed and calls template method // for each found userpic (from the top to the bottom) using enumerateItems() method. // - // Method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature + // Method has "bool (*Method)(gsl::not_null message, int userpicTop)" signature // if it returns false the enumeration stops immidiately. template void enumerateUserpics(Method method); @@ -160,7 +160,7 @@ private: // This function finds all date elements that are displayed and calls template method // for each found date element (from the bottom to the top) using enumerateItems() method. // - // Method has "bool (*Method)(HistoryItem *item, int itemtop, int dateTop)" signature + // Method has "bool (*Method)(gsl::not_null item, int itemtop, int dateTop)" signature // if it returns false the enumeration stops immidiately. template void enumerateDates(Method method); diff --git a/Telegram/SourceFiles/history/history_admin_log_item.cpp b/Telegram/SourceFiles/history/history_admin_log_item.cpp index c02cb70a8..689ac0282 100644 --- a/Telegram/SourceFiles/history/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/history_admin_log_item.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "history/history_admin_log_item.h" +#include "history/history_service.h" +#include "history/history_message.h" #include "history/history_admin_log_inner.h" #include "lang/lang_keys.h" #include "messenger.h" diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 2011cf25e..b338f9e46 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_history.h" #include "core/file_utilities.h" +#include "history/history_message.h" #include "history/history_service_layout.h" #include "history/history_media_types.h" #include "ui/widgets/popup_menu.h" @@ -270,7 +271,7 @@ void HistoryInner::enumerateUserpics(Method method) { // -1 means we didn't find an attached to next message yet. int lowestAttachedItemTop = -1; - auto userpicCallback = [this, &lowestAttachedItemTop, &method](HistoryItem *item, int itemtop, int itembottom) { + auto userpicCallback = [this, &lowestAttachedItemTop, &method](gsl::not_null item, int itemtop, int itembottom) { // Skip all service messages. auto message = item->toHistoryMessage(); if (!message) return true; @@ -318,7 +319,7 @@ void HistoryInner::enumerateDates(Method method) { // -1 means we didn't find a same-day with previous message yet. auto lowestInOneDayItemBottom = -1; - auto dateCallback = [this, &lowestInOneDayItemBottom, &method, drawtop](HistoryItem *item, int itemtop, int itembottom) { + auto dateCallback = [this, &lowestInOneDayItemBottom, &method, drawtop](gsl::not_null item, int itemtop, int itembottom) { if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) { lowestInOneDayItemBottom = itembottom - item->marginBottom(); } @@ -508,7 +509,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } if (mtop >= 0 || htop >= 0) { - enumerateUserpics([&p, &clip](HistoryMessage *message, int userpicTop) { + enumerateUserpics([&p, &clip](gsl::not_null message, int userpicTop) { // stop the enumeration if the userpic is below the painted rect if (userpicTop >= clip.top() + clip.height()) { return false; @@ -531,7 +532,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { //int showFloatingBefore = height() - 2 * (_visibleAreaBottom - _visibleAreaTop) - dateHeight; auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.); - enumerateDates([&p, &clip, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](HistoryItem *item, int itemtop, int dateTop) { + enumerateDates([&p, &clip, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](gsl::not_null item, int itemtop, int dateTop) { // stop the enumeration if the date is above the painted rect if (dateTop + dateHeight <= clip.top()) { return false; @@ -2038,7 +2039,7 @@ void HistoryInner::onUpdateSelected() { auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); auto scrollDateOpacity = _scrollDateOpacity.current(_scrollDateShown ? 1. : 0.); - enumerateDates([this, &dragState, &lnkhost, &point, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](HistoryItem *item, int itemtop, int dateTop) { + enumerateDates([this, &dragState, &lnkhost, &point, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](gsl::not_null item, int itemtop, int dateTop) { // stop enumeration if the date is above our point if (dateTop + dateHeight <= point.y()) { return false; @@ -2097,7 +2098,7 @@ void HistoryInner::onUpdateSelected() { if (!dragState.link && m.x() >= st::historyPhotoLeft && m.x() < st::historyPhotoLeft + st::msgPhotoSize) { if (auto msg = item->toHistoryMessage()) { if (msg->hasFromPhoto()) { - enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool { + enumerateUserpics([&dragState, &lnkhost, &point](gsl::not_null message, int userpicTop) -> bool { // stop enumeration if the userpic is below our point if (userpicTop > point.y()) { return false; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 882f78f56..282d94ff0 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -272,7 +272,7 @@ private: // This function finds all history items that are displayed and calls template method // for each found message (in given direction) in the passed history with passed top offset. // - // Method has "bool (*Method)(HistoryItem *item, int itemtop, int itembottom)" signature + // Method has "bool (*Method)(gsl::not_null item, int itemtop, int itembottom)" signature // if it returns false the enumeration stops immidiately. template void enumerateItemsInHistory(History *history, int historytop, Method method); @@ -292,7 +292,7 @@ private: // This function finds all userpics on the left that are displayed and calls template method // for each found userpic (from the top to the bottom) using enumerateItems() method. // - // Method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature + // Method has "bool (*Method)(gsl::not_null message, int userpicTop)" signature // if it returns false the enumeration stops immidiately. template void enumerateUserpics(Method method); @@ -300,7 +300,7 @@ private: // This function finds all date elements that are displayed and calls template method // for each found date element (from the bottom to the top) using enumerateItems() method. // - // Method has "bool (*Method)(HistoryItem *item, int itemtop, int dateTop)" signature + // Method has "bool (*Method)(gsl::not_null item, int itemtop, int dateTop)" signature // if it returns false the enumeration stops immidiately. template void enumerateDates(Method method); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 4d74486dc..b9fbf6bd6 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "history/history_service_layout.h" #include "history/history_media_types.h" +#include "history/history_message.h" #include "media/media_clip_reader.h" #include "styles/style_dialogs.h" #include "styles/style_history.h" @@ -562,6 +563,10 @@ HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr pointer) : _point } } +void HistoryMediaPtr::reset(std::unique_ptr pointer) { + *this = std::move(pointer); +} + HistoryMediaPtr &HistoryMediaPtr::operator=(std::unique_ptr pointer) { if (_pointer) { _pointer->detachFromParent(); @@ -573,6 +578,10 @@ HistoryMediaPtr &HistoryMediaPtr::operator=(std::unique_ptr pointe return *this; } +HistoryMediaPtr::~HistoryMediaPtr() { + reset(); +} + namespace internal { TextSelection unshiftSelection(TextSelection selection, const Text &byText) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index cf5bca761..f4e72599a 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -441,9 +441,7 @@ public: HistoryMedia *get() const { return _pointer.get(); } - void reset(std::unique_ptr pointer = nullptr) { - *this = std::move(pointer); - } + void reset(std::unique_ptr pointer = nullptr); bool isNull() const { return !_pointer; } @@ -458,9 +456,7 @@ public: explicit operator bool() const { return !isNull(); } - ~HistoryMediaPtr() { - reset(); - } + ~HistoryMediaPtr(); private: std::unique_ptr _pointer; diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index eeb13d241..58b5b2c9a 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -32,6 +32,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/add_contact_box.h" #include "core/click_handler_types.h" #include "history/history_location_manager.h" +#include "history/history_message.h" #include "window/window_controller.h" #include "styles/style_history.h" #include "calls/calls_instance.h" @@ -87,7 +88,7 @@ bool needReSetInlineResultDocument(const MTPMessageMedia &media, DocumentData *e } // namespace -void historyInitMedia() { +void HistoryInitMedia() { initTextOptions(); } @@ -593,6 +594,19 @@ TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const { return captionedSelectedText(lang(lng_in_dlg_photo), _caption, selection); } +bool HistoryPhoto::needsBubble() const { + if (!_caption.isEmpty()) { + return true; + } + if (auto message = _parent->toHistoryMessage()) { + return message->viaBot() + || message->Has() + || message->Has() + || message->displayFromName(); + } + return false; +} + int32 HistoryPhoto::addToOverview(AddToOverviewMethod method) { auto result = int32(0); if (_parent->toHistoryMessage()) { @@ -887,6 +901,19 @@ TextWithEntities HistoryVideo::selectedText(TextSelection selection) const { return captionedSelectedText(lang(lng_in_dlg_video), _caption, selection); } +bool HistoryVideo::needsBubble() const { + if (!_caption.isEmpty()) { + return true; + } + if (auto message = _parent->toHistoryMessage()) { + return message->viaBot() + || message->Has() + || message->Has() + || message->displayFromName(); + } + return false; +} + int32 HistoryVideo::addToOverview(AddToOverviewMethod method) { return addToOneOverview(OverviewVideos, method); } @@ -2284,6 +2311,22 @@ TextWithEntities HistoryGif::selectedText(TextSelection selection) const { return captionedSelectedText(mediaTypeString(), _caption, selection); } +bool HistoryGif::needsBubble() const { + if (_data->isRoundVideo()) { + return false; + } + if (!_caption.isEmpty()) { + return true; + } + if (auto message = _parent->toHistoryMessage()) { + return message->viaBot() + || message->Has() + || message->Has() + || message->displayFromName(); + } + return false; +} + int32 HistoryGif::addToOverview(AddToOverviewMethod method) { auto result = int32(0); if (_data->isRoundVideo()) { @@ -3156,11 +3199,13 @@ void HistoryWebPage::initDimensions() { } } + auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom(); + // init strings if (_description.isEmpty() && !_data->description.text.isEmpty()) { auto text = _data->description; - if (!_asArticle && !_attach) { + if (textFloatsAroundInfo) { text.text += _parent->skipBlock(); } auto opts = &_webpageDescriptionOptions; @@ -3172,7 +3217,7 @@ void HistoryWebPage::initDimensions() { _description.setMarkedText(st::webPageDescriptionStyle, text, *opts); } if (_title.isEmpty() && !title.isEmpty()) { - if (!_asArticle && !_attach && _description.isEmpty()) { + if (textFloatsAroundInfo && _description.isEmpty()) { title += _parent->skipBlock(); } _title.setText(st::webPageTitleStyle, title, _webpageTitleOptions); @@ -3218,7 +3263,7 @@ void HistoryWebPage::initDimensions() { if (!attachAtTop) _minh += st::mediaInBubbleSkip; _attach->initDimensions(); - QMargins bubble(_attach->bubbleMargins()); + auto bubble = _attach->bubbleMargins(); auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); if (isBubbleBottom() && _attach->customInfoLayout()) { maxMediaWidth += skipBlockWidth; @@ -3307,7 +3352,7 @@ int HistoryWebPage::resizeGetHeight(int width) { if (_description.isEmpty()) { _descriptionLines = 0; } else { - int32 descriptionHeight = _description.countHeight(width); + auto descriptionHeight = _description.countHeight(width); if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { _descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height); } else { @@ -3320,7 +3365,7 @@ int HistoryWebPage::resizeGetHeight(int width) { auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; if (!attachAtTop) _height += st::mediaInBubbleSkip; - QMargins bubble(_attach->bubbleMargins()); + auto bubble = _attach->bubbleMargins(); _attach->resizeGetHeight(width + bubble.left() + bubble.right()); _height += _attach->height() - bubble.top() - bubble.bottom(); @@ -4636,6 +4681,19 @@ TextWithEntities HistoryLocation::selectedText(TextSelection selection) const { return titleResult; } +bool HistoryLocation::needsBubble() const { + if (!_title.isEmpty() || !_description.isEmpty()) { + return true; + } + if (auto message = _parent->toHistoryMessage()) { + return message->viaBot() + || message->Has() + || message->Has() + || message->displayFromName(); + } + return false; +} + int32 HistoryLocation::fullWidth() const { return st::locationSize.width(); } diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h index b2626e7f8..81e480e41 100644 --- a/Telegram/SourceFiles/history/history_media_types.h +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once +#include "history/history_media.h" #include "ui/effects/radial_animation.h" namespace Media { @@ -28,7 +29,7 @@ class Playback; } // namespace Clip } // namespace Media -void historyInitMedia(); +void HistoryInitMedia(); class HistoryFileMedia : public HistoryMedia { public: @@ -167,18 +168,7 @@ public: TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } - bool needsBubble() const override { - if (!_caption.isEmpty()) { - return true; - } - if (auto message = _parent->toHistoryMessage()) { - return message->viaBot() - || message->Has() - || message->Has() - || message->displayFromName(); - } - return false; - } + bool needsBubble() const override; bool customInfoLayout() const override { return _caption.isEmpty(); } @@ -264,18 +254,7 @@ public: TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } - bool needsBubble() const override { - if (!_caption.isEmpty()) { - return true; - } - if (auto message = _parent->toHistoryMessage()) { - return message->viaBot() - || message->Has() - || message->Has() - || message->displayFromName(); - } - return false; - } + bool needsBubble() const override; bool customInfoLayout() const override { return _caption.isEmpty(); } @@ -536,21 +515,7 @@ public: TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } - bool needsBubble() const override { - if (_data->isRoundVideo()) { - return false; - } - if (!_caption.isEmpty()) { - return true; - } - if (auto message = _parent->toHistoryMessage()) { - return message->viaBot() - || message->Has() - || message->Has() - || message->displayFromName(); - } - return false; - } + bool needsBubble() const override; bool customInfoLayout() const override { return _caption.isEmpty(); } @@ -1109,18 +1074,7 @@ public: QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; - bool needsBubble() const override { - if (!_title.isEmpty() || !_description.isEmpty()) { - return true; - } - if (auto message = _parent->toHistoryMessage()) { - return message->viaBot() - || message->Has() - || message->Has() - || message->displayFromName(); - } - return false; - } + bool needsBubble() const override; bool customInfoLayout() const override { return true; } diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 2c279b365..04008eb5b 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "history/history_location_manager.h" #include "history/history_service_layout.h" #include "history/history_media_types.h" +#include "history/history_service.h" #include "auth_session.h" #include "styles/style_dialogs.h" #include "styles/style_widgets.h" @@ -37,26 +38,11 @@ namespace { constexpr auto kPinnedMessageTextLimit = 16; -TextParseOptions _historySrvOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags/* | TextParseMultiline*/ | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // lang-dependent -}; - inline void initTextOptions() { _historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir(); _textDlgOptions.maxw = st::dialogsWidthMax * 2; } -ApiWrap::RequestMessageDataCallback historyDependentItemCallback(const FullMsgId &msgId) { - return [dependent = msgId](ChannelData *channel, MsgId msgId) { - if (auto item = App::histItemById(dependent)) { - item->updateDependencyItem(); - } - }; -} - style::color fromNameFg(int index) { Expects(index >= 0 && index < 8); style::color colors[] = { @@ -87,8 +73,8 @@ style::color fromNameFgSelected(int index) { return colors[index]; } -MTPDmessage::Flags newForwardedFlags(PeerData *peer, int32 from, HistoryMessage *fwd) { - auto result = newMessageFlags(peer) | MTPDmessage::Flag::f_fwd_from; +MTPDmessage::Flags NewForwardedFlags(gsl::not_null peer, int32 from, gsl::not_null fwd) { + auto result = NewMessageFlags(peer) | MTPDmessage::Flag::f_fwd_from; if (from) { result |= MTPDmessage::Flag::f_from_id; } @@ -121,7 +107,26 @@ MTPDmessage::Flags newForwardedFlags(PeerData *peer, int32 from, HistoryMessage } // namespace -void historyInitMessages() { +base::lambda HistoryDependentItemCallback(const FullMsgId &msgId) { + return [dependent = msgId](ChannelData *channel, MsgId msgId) { + if (auto item = App::histItemById(dependent)) { + item->updateDependencyItem(); + } + }; +} + +MTPDmessage::Flags NewMessageFlags(gsl::not_null peer) { + MTPDmessage::Flags result = 0; + if (!peer->isSelf()) { + result |= MTPDmessage::Flag::f_out; + //if (p->isChat() || (p->isUser() && !p->asUser()->botInfo)) { + // result |= MTPDmessage::Flag::f_unread; + //} + } + return result; +} + +void HistoryInitMessages() { initTextOptions(); } @@ -451,8 +456,8 @@ HistoryMessage::HistoryMessage(gsl::not_null history, const MTPDmessag setText(TextWithEntities {}); } -HistoryMessage::HistoryMessage(gsl::not_null history, MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) -: HistoryItem(history, id, newForwardedFlags(history->peer, from, fwd) | flags, date, from) { +HistoryMessage::HistoryMessage(gsl::not_null history, MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, gsl::not_null fwd) +: HistoryItem(history, id, NewForwardedFlags(history->peer, from, fwd) | flags, date, from) { CreateConfig config; if (fwd->Has() || !fwd->history()->peer->isSelf()) { @@ -596,6 +601,10 @@ bool HistoryMessage::displayEditedBadge(bool hasViaBotOrInlineMarkup) const { return true; } +bool HistoryMessage::uploading() const { + return _media && _media->uploading(); +} + void HistoryMessage::createComponents(const CreateConfig &config) { uint64 mask = 0; if (config.replyTo) { @@ -638,7 +647,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) { if (auto reply = Get()) { reply->replyToMsgId = config.replyTo; if (!reply->updateData(this) && App::api()) { - App::api()->requestMessageData(history()->peer->asChannel(), reply->replyToMsgId, historyDependentItemCallback(fullId())); + App::api()->requestMessageData(history()->peer->asChannel(), reply->replyToMsgId, HistoryDependentItemCallback(fullId())); } } if (auto via = Get()) { @@ -793,71 +802,84 @@ int32 HistoryMessage::plainMaxWidth() const { } void HistoryMessage::initDimensions() { - auto reply = Get(); - if (reply) { - reply->updateName(); - } - updateMediaInBubbleState(); if (drawBubble()) { auto forwarded = Get(); + auto reply = Get(); auto via = Get(); + auto entry = Get(); if (forwarded) { forwarded->create(via); } + if (reply) { + reply->updateName(); + } auto mediaDisplayed = false; if (_media) { mediaDisplayed = _media->isDisplayed(); _media->initDimensions(); - if (mediaDisplayed && _media->isBubbleBottom()) { - if (_text.hasSkipBlock()) { - _text.removeSkipBlock(); - _textWidth = -1; - _textHeight = 0; - } - } else if (!_text.hasSkipBlock()) { - _text.setSkipBlock(skipBlockWidth(), skipBlockHeight()); - _textWidth = -1; - _textHeight = 0; - } } - auto entry = Get(); if (entry) { entry->_page->initDimensions(); } + // Entry page is always a bubble bottom. + auto mediaOnBottom = (mediaDisplayed && _media->isBubbleBottom()) || (entry/* && entry->_page->isBubbleBottom()*/); + auto mediaOnTop = (mediaDisplayed && _media->isBubbleTop()) || (entry && entry->_page->isBubbleTop()); + + if (mediaDisplayed && mediaOnBottom) { + if (_text.hasSkipBlock()) { + _text.removeSkipBlock(); + _textWidth = -1; + _textHeight = 0; + } + } else if (!_text.hasSkipBlock()) { + _text.setSkipBlock(skipBlockWidth(), skipBlockHeight()); + _textWidth = -1; + _textHeight = 0; + } + _maxw = plainMaxWidth(); _minh = emptyText() ? 0 : _text.minHeight(); + if (!mediaOnBottom) { + _minh += st::msgPadding.bottom(); + if (mediaDisplayed) _minh += st::mediaInBubbleSkip; + } + if (!mediaOnTop) { + _minh += st::msgPadding.top(); + if (mediaDisplayed) _minh += st::mediaInBubbleSkip; + if (entry) _minh += st::mediaInBubbleSkip; + } 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; + // Parts don't participate in maxWidth() in case of media message. + accumulate_max(_maxw, _media->maxWidth()); _minh += _media->minHeight(); } else { - _minh += st::msgPadding.top() + st::msgPadding.bottom(); + // Count parts in maxWidth(), don't count them in minHeight(). + // They will be added in resizeGetHeight() anyway. if (displayFromName()) { auto namew = st::msgPadding.left() + author()->nameText.maxWidth() + st::msgPadding.right(); if (via && !forwarded) { namew += st::msgServiceFont->spacew + via->_maxWidth; } - if (namew > _maxw) _maxw = namew; + accumulate_max(_maxw, namew); } else if (via && !forwarded) { - if (st::msgPadding.left() + via->_maxWidth + st::msgPadding.right() > _maxw) { - _maxw = st::msgPadding.left() + via->_maxWidth + st::msgPadding.right(); - } + accumulate_max(_maxw, st::msgPadding.left() + via->_maxWidth + st::msgPadding.right()); } if (forwarded) { - auto _namew = st::msgPadding.left() + forwarded->_text.maxWidth() + st::msgPadding.right(); + auto namew = st::msgPadding.left() + forwarded->_text.maxWidth() + st::msgPadding.right(); if (via) { - _namew += st::msgServiceFont->spacew + via->_maxWidth; + namew += st::msgServiceFont->spacew + via->_maxWidth; } - if (_namew > _maxw) _maxw = _namew; + accumulate_max(_maxw, namew); + } + if (reply) { + auto replyw = st::msgPadding.left() + reply->_maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); + if (reply->_replyToVia) { + replyw += st::msgServiceFont->spacew + reply->_replyToVia->_maxWidth; + } + accumulate_max(_maxw, replyw); } if (entry) { accumulate_max(_maxw, entry->_page->maxWidth()); @@ -874,22 +896,15 @@ void HistoryMessage::initDimensions() { _maxw = st::msgMinWidth; _minh = 0; } - if (reply && !emptyText()) { - int replyw = st::msgPadding.left() + reply->_maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); - if (reply->_replyToVia) { - replyw += st::msgServiceFont->spacew + reply->_replyToVia->_maxWidth; - } - if (replyw > _maxw) _maxw = replyw; - } if (auto markup = inlineReplyMarkup()) { if (!markup->inlineKeyboard) { - markup->inlineKeyboard.reset(new ReplyKeyboard(this, std::make_unique(st::msgBotKbButton))); + markup->inlineKeyboard = std::make_unique(this, std::make_unique(st::msgBotKbButton)); } // if we have a text bubble we can resize it to fit the keyboard // but if we have only media we don't do that if (!emptyText()) { - _maxw = qMax(_maxw, markup->inlineKeyboard->naturalWidth()); + accumulate_max(_maxw, markup->inlineKeyboard->naturalWidth()); } } } @@ -986,6 +1001,17 @@ void HistoryMessage::applyEditionToEmpty() { finishEditionToEmpty(); } +bool HistoryMessage::displayForwardedFrom() const { + if (auto forwarded = Get()) { + return Has() + || !_media + || !_media->isDisplayed() + || !_media->hideForwardedFrom() + || forwarded->_authorOriginal->isChannel(); + } + return false; +} + void HistoryMessage::updateMedia(const MTPMessageMedia *media) { auto setMediaAllowed = [](HistoryMediaType type) { return (type == MediaTypeWebPage || type == MediaTypeGame || type == MediaTypeLocation); @@ -1387,8 +1413,15 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM auto displayTail = skipTail ? RectPart::None : (outbg && !Adaptive::ChatWide()) ? RectPart::Right : RectPart::Left; HistoryLayout::paintBubble(p, g, width(), selected, outbg, displayTail); - auto trect = g.marginsAdded(-st::msgPadding); - if (mediaDisplayed && _media->isBubbleTop()) { + // Entry page is always a bubble bottom. + auto mediaOnBottom = (mediaDisplayed && _media->isBubbleBottom()) || (entry/* && entry->_page->isBubbleBottom()*/); + auto mediaOnTop = (mediaDisplayed && _media->isBubbleTop()) || (entry && entry->_page->isBubbleTop()); + + auto trect = g.marginsRemoved(st::msgPadding); + if (mediaOnBottom) { + trect.setHeight(trect.height() + st::msgPadding.bottom()); + } + if (mediaOnTop) { trect.setY(trect.y() - st::msgPadding.top()); } else { paintFromName(p, trect, selected); @@ -1396,13 +1429,10 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM paintReplyInfo(p, trect, selected); paintViaBotIdInfo(p, trect, selected); } - if (mediaDisplayed && _media->isBubbleBottom()) { - trect.setHeight(trect.height() + st::msgPadding.bottom()); - } if (entry) { trect.setHeight(trect.height() - entry->_page->height()); } - auto needDrawInfo = true; + auto needDrawInfo = mediaOnBottom ? !(entry ? entry->_page->customInfoLayout() : _media->customInfoLayout()) : true; if (mediaDisplayed) { auto mediaAboveText = _media->isAboveMessage(); auto mediaHeight = _media->height(); @@ -1418,9 +1448,9 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM if (mediaAboveText) { trect.setY(trect.y() + mediaHeight); paintText(p, trect, selection); + } else { + needDrawInfo = !_media->customInfoLayout(); } - - needDrawInfo = !_media->customInfoLayout(); } else { paintText(p, trect, selection); } @@ -1563,11 +1593,14 @@ int HistoryMessage::performResizeGetHeight() { auto entry = Get(); auto mediaDisplayed = false; - auto mediaInBubbleState = MediaInBubbleState::None; if (_media) { mediaDisplayed = _media->isDisplayed(); - mediaInBubbleState = _media->inBubbleState(); } + + // Entry page is always a bubble bottom. + auto mediaOnBottom = (mediaDisplayed && _media->isBubbleBottom()) || (entry/* && entry->_page->isBubbleBottom()*/); + auto mediaOnTop = (mediaDisplayed && _media->isBubbleTop()) || (entry && entry->_page->isBubbleTop()); + if (contentWidth >= _maxw) { _height = _minh; if (mediaDisplayed) { @@ -1587,16 +1620,17 @@ int HistoryMessage::performResizeGetHeight() { } _height = _textHeight; } + if (!mediaOnBottom) { + _height += st::msgPadding.bottom(); + if (mediaDisplayed) _height += st::mediaInBubbleSkip; + } + if (!mediaOnTop) { + _height += st::msgPadding.top(); + if (mediaDisplayed) _height += st::mediaInBubbleSkip; + if (entry) _height += st::mediaInBubbleSkip; + } if (mediaDisplayed) { - if (!_media->isBubbleTop()) { - _height += st::msgPadding.top() + st::mediaInBubbleSkip; - } - if (!_media->isBubbleBottom()) { - _height += st::msgPadding.bottom() + st::mediaInBubbleSkip; - } _height += _media->resizeGetHeight(contentWidth); - } else { - _height += st::msgPadding.top() + st::msgPadding.bottom(); } if (entry) { _height += entry->_page->resizeGetHeight(contentWidth); @@ -1887,6 +1921,16 @@ TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelec return { textSelection.from, fromMediaSelection(mediaSelection).to }; } +void HistoryMessage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (_media) _media->clickHandlerActiveChanged(p, active); + HistoryItem::clickHandlerActiveChanged(p, active); +} + +void HistoryMessage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (_media) _media->clickHandlerPressedChanged(p, pressed); + HistoryItem::clickHandlerPressedChanged(p, pressed); +} + QString HistoryMessage::notificationHeader() const { return (!_history->peer->isUser() && !isPost()) ? from()->name : QString(); } @@ -1905,644 +1949,3 @@ HistoryMessage::~HistoryMessage() { reply->clearData(this); } } - -void HistoryService::setMessageByAction(const MTPmessageAction &action) { - auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) { - auto result = PreparedText {}; - auto &users = action.vusers.v; - if (users.size() == 1) { - auto u = App::user(peerFromUser(users[0])); - if (u == _from) { - result.links.push_back(fromLink()); - result.text = lng_action_user_joined(lt_from, fromLinkText()); - } else { - result.links.push_back(fromLink()); - result.links.push_back(peerOpenClickHandler(u)); - result.text = lng_action_add_user(lt_from, fromLinkText(), lt_user, textcmdLink(2, u->name)); - } - } else if (users.isEmpty()) { - result.links.push_back(fromLink()); - result.text = lng_action_add_user(lt_from, fromLinkText(), lt_user, "somebody"); - } else { - result.links.push_back(fromLink()); - for (auto i = 0, l = users.size(); i != l; ++i) { - auto user = App::user(peerFromUser(users[i])); - result.links.push_back(peerOpenClickHandler(user)); - - auto linkText = textcmdLink(i + 2, user->name); - if (i == 0) { - result.text = linkText; - } else if (i + 1 == l) { - result.text = lng_action_add_users_and_last(lt_accumulated, result.text, lt_user, linkText); - } else { - result.text = lng_action_add_users_and_one(lt_accumulated, result.text, lt_user, linkText); - } - } - result.text = lng_action_add_users_many(lt_from, fromLinkText(), lt_users, result.text); - } - return result; - }; - - auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) { - auto result = PreparedText {}; - result.links.push_back(fromLink()); - result.text = lng_action_user_joined_by_link(lt_from, fromLinkText()); - return result; - }; - - auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) { - auto result = PreparedText {}; - result.links.push_back(fromLink()); - result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle))); - return result; - }; - - auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) { - auto result = PreparedText {}; - if (isPost()) { - result.text = lang(lng_action_created_channel); - } else { - result.links.push_back(fromLink()); - result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle))); - } - return result; - }; - - auto prepareChatDeletePhoto = [this] { - auto result = PreparedText {}; - if (isPost()) { - result.text = lang(lng_action_removed_photo_channel); - } else { - result.links.push_back(fromLink()); - result.text = lng_action_removed_photo(lt_from, fromLinkText()); - } - return result; - }; - - auto prepareChatDeleteUser = [this](const MTPDmessageActionChatDeleteUser &action) { - auto result = PreparedText {}; - if (peerFromUser(action.vuser_id) == _from->id) { - result.links.push_back(fromLink()); - result.text = lng_action_user_left(lt_from, fromLinkText()); - } else { - auto user = App::user(peerFromUser(action.vuser_id)); - result.links.push_back(fromLink()); - result.links.push_back(peerOpenClickHandler(user)); - result.text = lng_action_kick_user(lt_from, fromLinkText(), lt_user, textcmdLink(2, user->name)); - } - return result; - }; - - auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) { - auto result = PreparedText {}; - if (isPost()) { - result.text = lang(lng_action_changed_photo_channel); - } else { - result.links.push_back(fromLink()); - result.text = lng_action_changed_photo(lt_from, fromLinkText()); - } - return result; - }; - - auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) { - auto result = PreparedText {}; - if (isPost()) { - result.text = lng_action_changed_title_channel(lt_title, textClean(qs(action.vtitle))); - } else { - result.links.push_back(fromLink()); - result.text = lng_action_changed_title(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle))); - } - return result; - }; - - auto messageText = PreparedText {}; - - switch (action.type()) { - case mtpc_messageActionChatAddUser: messageText = prepareChatAddUserText(action.c_messageActionChatAddUser()); break; - case mtpc_messageActionChatJoinedByLink: messageText = prepareChatJoinedByLink(action.c_messageActionChatJoinedByLink()); break; - case mtpc_messageActionChatCreate: messageText = prepareChatCreate(action.c_messageActionChatCreate()); break; - case mtpc_messageActionChannelCreate: messageText = prepareChannelCreate(action.c_messageActionChannelCreate()); break; - case mtpc_messageActionHistoryClear: break; // Leave empty text. - case mtpc_messageActionChatDeletePhoto: messageText = prepareChatDeletePhoto(); break; - case mtpc_messageActionChatDeleteUser: messageText = prepareChatDeleteUser(action.c_messageActionChatDeleteUser()); break; - case mtpc_messageActionChatEditPhoto: messageText = prepareChatEditPhoto(action.c_messageActionChatEditPhoto()); break; - case mtpc_messageActionChatEditTitle: messageText = prepareChatEditTitle(action.c_messageActionChatEditTitle()); break; - case mtpc_messageActionChatMigrateTo: messageText.text = lang(lng_action_group_migrate); break; - case mtpc_messageActionChannelMigrateFrom: messageText.text = lang(lng_action_group_migrate); break; - case mtpc_messageActionPinMessage: messageText = preparePinnedText(); break; - case mtpc_messageActionGameScore: messageText = prepareGameScoreText(); break; - case mtpc_messageActionPhoneCall: Unexpected("PhoneCall type in HistoryService."); - case mtpc_messageActionPaymentSent: messageText = preparePaymentSentText(); break; - default: messageText.text = lang(lng_message_empty); break; - } - - setServiceText(messageText); - - // Additional information. - switch (action.type()) { - case mtpc_messageActionChatAddUser: { - if (auto channel = history()->peer->asMegagroup()) { - auto &users = action.c_messageActionChatAddUser().vusers; - for_const (auto &item, users.v) { - if (item.v == AuthSession::CurrentUserId()) { - channel->mgInfo->joinedMessageFound = true; - break; - } - } - } - } break; - - case mtpc_messageActionChatJoinedByLink: { - if (_from->isSelf() && history()->peer->isMegagroup()) { - history()->peer->asChannel()->mgInfo->joinedMessageFound = true; - } - } break; - - case mtpc_messageActionChatEditPhoto: { - auto &photo = action.c_messageActionChatEditPhoto().vphoto; - if (photo.type() == mtpc_photo) { - _media = std::make_unique(this, history()->peer, photo.c_photo(), st::msgServicePhotoWidth); - } - } break; - - case mtpc_messageActionChatMigrateTo: - case mtpc_messageActionChannelMigrateFrom: { - _flags |= MTPDmessage_ClientFlag::f_is_group_migrate; - } break; - } -} - -bool HistoryService::updateDependent(bool force) { - auto dependent = GetDependentData(); - t_assert(dependent != nullptr); - - if (!force) { - if (!dependent->msgId || dependent->msg) { - return true; - } - } - - if (!dependent->lnk) { - dependent->lnk = goToMessageClickHandler(history()->peer, dependent->msgId); - } - bool gotDependencyItem = false; - if (!dependent->msg) { - dependent->msg = App::histItemById(channelId(), dependent->msgId); - if (dependent->msg) { - App::historyRegDependency(this, dependent->msg); - gotDependencyItem = true; - } - } - if (dependent->msg) { - updateDependentText(); - } else if (force) { - if (dependent->msgId > 0) { - dependent->msgId = 0; - gotDependencyItem = true; - } - updateDependentText(); - } - if (force && gotDependencyItem) { - AuthSession::Current().notifications().checkDelayed(); - } - return (dependent->msg || !dependent->msgId); -} - -HistoryService::PreparedText HistoryService::preparePinnedText() { - auto result = PreparedText {}; - auto pinned = Get(); - if (pinned && pinned->msg) { - auto mediaText = ([pinned]() -> QString { - auto media = pinned->msg->getMedia(); - switch (media ? media->type() : MediaTypeCount) { - case MediaTypePhoto: return lang(lng_action_pinned_media_photo); - case MediaTypeVideo: return lang(lng_action_pinned_media_video); - case MediaTypeContact: return lang(lng_action_pinned_media_contact); - case MediaTypeFile: return lang(lng_action_pinned_media_file); - case MediaTypeGif: { - if (auto document = media->getDocument()) { - if (document->isRoundVideo()) { - return lang(lng_action_pinned_media_video_message); - } - } - return lang(lng_action_pinned_media_gif); - } break; - case MediaTypeSticker: { - auto emoji = static_cast(media)->emoji(); - if (emoji.isEmpty()) { - return lang(lng_action_pinned_media_sticker); - } - return lng_action_pinned_media_emoji_sticker(lt_emoji, emoji); - } break; - case MediaTypeLocation: return lang(lng_action_pinned_media_location); - case MediaTypeMusicFile: return lang(lng_action_pinned_media_audio); - case MediaTypeVoiceFile: return lang(lng_action_pinned_media_voice); - case MediaTypeGame: { - auto title = static_cast(media)->game()->title; - return lng_action_pinned_media_game(lt_game, title); - } break; - } - return QString(); - })(); - - result.links.push_back(fromLink()); - result.links.push_back(pinned->lnk); - if (mediaText.isEmpty()) { - auto original = pinned->msg->originalText().text; - auto cutAt = 0; - auto limit = kPinnedMessageTextLimit; - auto size = original.size(); - for (; limit != 0;) { - --limit; - if (cutAt >= size) break; - if (original.at(cutAt).isLowSurrogate() && cutAt + 1 < size && original.at(cutAt + 1).isHighSurrogate()) { - cutAt += 2; - } else { - ++cutAt; - } - } - if (!limit && cutAt + 5 < size) { - original = original.mid(0, cutAt) + qstr("..."); - } - result.text = lng_action_pinned_message(lt_from, fromLinkText(), lt_text, textcmdLink(2, original)); - } else { - result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, textcmdLink(2, mediaText)); - } - } else if (pinned && pinned->msgId) { - result.links.push_back(fromLink()); - result.links.push_back(pinned->lnk); - result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, textcmdLink(2, lang(lng_contacts_loading))); - } else { - result.links.push_back(fromLink()); - result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, lang(lng_deleted_message)); - } - return result; -} - -HistoryService::PreparedText HistoryService::prepareGameScoreText() { - auto result = PreparedText {}; - auto gamescore = Get(); - - auto computeGameTitle = [gamescore, &result]() -> QString { - if (gamescore && gamescore->msg) { - if (auto media = gamescore->msg->getMedia()) { - if (media->type() == MediaTypeGame) { - result.links.push_back(MakeShared(gamescore->msg, 0, 0)); - auto titleText = static_cast(media)->game()->title; - return textcmdLink(result.links.size(), titleText); - } - } - return lang(lng_deleted_message); - } else if (gamescore && gamescore->msgId) { - return lang(lng_contacts_loading); - } - return QString(); - }; - - auto scoreNumber = gamescore ? gamescore->score : 0; - if (_from->isSelf()) { - auto gameTitle = computeGameTitle(); - if (gameTitle.isEmpty()) { - result.text = lng_action_game_you_scored_no_game(lt_count, scoreNumber); - } else { - result.text = lng_action_game_you_scored(lt_count, scoreNumber, lt_game, gameTitle); - } - } else { - result.links.push_back(fromLink()); - auto gameTitle = computeGameTitle(); - if (gameTitle.isEmpty()) { - result.text = lng_action_game_score_no_game(lt_count, scoreNumber, lt_from, fromLinkText()); - } else { - result.text = lng_action_game_score(lt_count, scoreNumber, lt_from, fromLinkText(), lt_game, gameTitle); - } - } - return result; -} - -HistoryService::PreparedText HistoryService::preparePaymentSentText() { - auto result = PreparedText {}; - auto payment = Get(); - - auto invoiceTitle = ([payment]() -> QString { - if (payment && payment->msg) { - if (auto media = payment->msg->getMedia()) { - if (media->type() == MediaTypeInvoice) { - return static_cast(media)->getTitle(); - } - } - return lang(lng_deleted_message); - } else if (payment && payment->msgId) { - return lang(lng_contacts_loading); - } - return QString(); - })(); - - if (invoiceTitle.isEmpty()) { - result.text = lng_action_payment_done(lt_amount, payment->amount, lt_user, history()->peer->name); - } else { - result.text = lng_action_payment_done_for(lt_amount, payment->amount, lt_user, history()->peer->name, lt_invoice, invoiceTitle); - } - return result; -} - -HistoryService::HistoryService(gsl::not_null history, const MTPDmessageService &message) : - HistoryItem(history, message.vid.v, mtpCastFlags(message.vflags.v), ::date(message.vdate), message.has_from_id() ? message.vfrom_id.v : 0) { - createFromMtp(message); - setMessageByAction(message.vaction); -} - -HistoryService::HistoryService(gsl::not_null history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from, PhotoData *photo) : - HistoryItem(history, msgId, flags, date, from) { - setServiceText(message); - if (photo) { - _media = std::make_unique(this, history->peer, photo, st::msgServicePhotoWidth); - } -} - -void HistoryService::initDimensions() { - _maxw = _text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); - _minh = _text.minHeight(); - if (_media) _media->initDimensions(); -} - -bool HistoryService::updateDependencyItem() { - if (GetDependentData()) { - return updateDependent(true); - } - return HistoryItem::updateDependencyItem(); -} - -QRect HistoryService::countGeometry() const { - auto result = QRect(0, 0, width(), _height); - if (Adaptive::ChatWide()) { - result.setWidth(qMin(result.width(), st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); - } - return result.marginsRemoved(st::msgServiceMargin); -} - -TextWithEntities HistoryService::selectedText(TextSelection selection) const { - return _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection); -} - -QString HistoryService::inDialogsText() const { - return textcmdLink(1, textClean(notificationText())); -} - -QString HistoryService::inReplyText() const { - QString result = HistoryService::notificationText(); - return result.trimmed().startsWith(author()->name) ? result.trimmed().mid(author()->name.size()).trimmed() : result; -} - -void HistoryService::setServiceText(const PreparedText &prepared) { - _text.setText(st::serviceTextStyle, prepared.text, _historySrvOptions); - auto linkIndex = 0; - for_const (auto &link, prepared.links) { - // Link indices start with 1. - _text.setLink(++linkIndex, link); - } - - setPendingInitDimensions(); - _textWidth = -1; - _textHeight = 0; -} - -void HistoryService::draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms) const { - auto height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); - auto dateh = 0; - auto unreadbarh = 0; - if (auto date = Get()) { - dateh = date->height(); - p.translate(0, dateh); - clip.translate(0, -dateh); - height -= dateh; - } - if (auto unreadbar = Get()) { - unreadbarh = unreadbar->height(); - if (clip.intersects(QRect(0, 0, width(), unreadbarh))) { - unreadbar->paint(p, 0, width()); - } - p.translate(0, unreadbarh); - clip.translate(0, -unreadbarh); - height -= unreadbarh; - } - - HistoryLayout::PaintContext context(ms, clip, selection); - HistoryLayout::ServiceMessagePainter::paint(p, this, context, height); - - if (auto skiph = dateh + unreadbarh) { - p.translate(0, -skiph); - } -} - -int HistoryService::resizeContentGetHeight() { - _height = displayedDateHeight(); - if (auto unreadbar = Get()) { - _height += unreadbar->height(); - } - - if (_text.isEmpty()) { - _textHeight = 0; - } else { - auto contentWidth = width(); - if (Adaptive::ChatWide()) { - accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); - } - contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins - if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) { - contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1; - } - - auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0); - if (nwidth != _textWidth) { - _textWidth = nwidth; - _textHeight = _text.countHeight(nwidth); - } - if (contentWidth >= _maxw) { - _height += _minh; - } else { - _height += _textHeight; - } - _height += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); - if (_media) { - _height += st::msgServiceMargin.top() + _media->resizeGetHeight(_media->currentWidth()); - } - } - - return _height; -} - -bool HistoryService::hasPoint(QPoint point) const { - auto g = countGeometry(); - if (g.width() < 1) { - return false; - } - - if (auto dateh = displayedDateHeight()) { - g.setTop(g.top() + dateh); - } - if (auto unreadbar = Get()) { - g.setTop(g.top() + unreadbar->height()); - } - if (_media) { - g.setHeight(g.height() - (st::msgServiceMargin.top() + _media->height())); - } - return g.contains(point); -} - -HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest request) const { - HistoryTextState result; - - auto g = countGeometry(); - if (g.width() < 1) { - return result; - } - - if (auto dateh = displayedDateHeight()) { - point.setY(point.y() - dateh); - g.setHeight(g.height() - dateh); - } - if (auto unreadbar = Get()) { - auto unreadbarh = unreadbar->height(); - point.setY(point.y() - unreadbarh); - g.setHeight(g.height() - unreadbarh); - } - - if (_media) { - g.setHeight(g.height() - (st::msgServiceMargin.top() + _media->height())); - } - auto trect = g.marginsAdded(-st::msgServicePadding); - if (trect.contains(point)) { - auto textRequest = request.forText(); - textRequest.align = style::al_center; - result = _text.getState(point - trect.topLeft(), trect.width(), textRequest); - if (auto gamescore = Get()) { - if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) { - result.link = gamescore->lnk; - } - } else if (auto payment = Get()) { - if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) { - result.link = payment->lnk; - } - } - } else if (_media) { - result = _media->getState(point - QPoint(st::msgServiceMargin.left() + (g.width() - _media->maxWidth()) / 2, st::msgServiceMargin.top() + g.height() + st::msgServiceMargin.top()), request); - } - return result; -} - -void HistoryService::createFromMtp(const MTPDmessageService &message) { - if (message.vaction.type() == mtpc_messageActionGameScore) { - UpdateComponents(HistoryServiceGameScore::Bit()); - Get()->score = message.vaction.c_messageActionGameScore().vscore.v; - } else if (message.vaction.type() == mtpc_messageActionPaymentSent) { - UpdateComponents(HistoryServicePayment::Bit()); - auto amount = message.vaction.c_messageActionPaymentSent().vtotal_amount.v; - auto currency = qs(message.vaction.c_messageActionPaymentSent().vcurrency); - Get()->amount = HistoryInvoice::fillAmountAndCurrency(amount, currency); - } - if (message.has_reply_to_msg_id()) { - if (message.vaction.type() == mtpc_messageActionPinMessage) { - UpdateComponents(HistoryServicePinned::Bit()); - } - if (auto dependent = GetDependentData()) { - dependent->msgId = message.vreply_to_msg_id.v; - if (!updateDependent() && App::api()) { - App::api()->requestMessageData(history()->peer->asChannel(), dependent->msgId, historyDependentItemCallback(fullId())); - } - } - } - setMessageByAction(message.vaction); -} - -void HistoryService::applyEdition(const MTPDmessageService &message) { - clearDependency(); - UpdateComponents(0); - - createFromMtp(message); - - if (message.vaction.type() == mtpc_messageActionHistoryClear) { - removeMedia(); - finishEditionToEmpty(); - } else { - finishEdition(-1); - } -} - -void HistoryService::removeMedia() { - if (!_media) return; - - bool mediaWasDisplayed = _media->isDisplayed(); - _media.reset(); - if (mediaWasDisplayed) { - _textWidth = -1; - _textHeight = 0; - } -} - -int32 HistoryService::addToOverview(AddToOverviewMethod method) { - if (!indexInOverview()) return 0; - - int32 result = 0; - if (auto media = getMedia()) { - result |= media->addToOverview(method); - } - return result; -} - -void HistoryService::eraseFromOverview() { - if (auto media = getMedia()) { - media->eraseFromOverview(); - } -} - -void HistoryService::updateDependentText() { - auto text = PreparedText {}; - if (Has()) { - text = preparePinnedText(); - } else if (Has()) { - text = prepareGameScoreText(); - } else if (Has()) { - text = preparePaymentSentText(); - } else { - return; - } - - setServiceText(text); - if (history()->textCachedFor == this) { - history()->textCachedFor = nullptr; - } - if (App::main()) { - App::main()->dlgUpdated(history()->peer, id); - } - App::historyUpdateDependent(this); -} - -void HistoryService::clearDependency() { - if (auto dependent = GetDependentData()) { - if (dependent->msg) { - App::historyUnregDependency(this, dependent->msg); - } - } -} - -HistoryService::~HistoryService() { - clearDependency(); - _media.reset(); -} - -HistoryJoined::HistoryJoined(gsl::not_null history, const QDateTime &inviteDate, gsl::not_null inviter, MTPDmessage::Flags flags) - : HistoryService(history, clientMsgId(), inviteDate, GenerateText(history, inviter), flags) { -} - -HistoryJoined::PreparedText HistoryJoined::GenerateText(gsl::not_null history, gsl::not_null inviter) { - if (inviter->id == AuthSession::CurrentUserPeerId()) { - return { lang(history->isMegagroup() ? lng_action_you_joined_group : lng_action_you_joined) }; - } - auto result = PreparedText {}; - result.links.push_back(peerOpenClickHandler(inviter)); - if (history->isMegagroup()) { - result.text = lng_action_add_you_group(lt_from, textcmdLink(1, inviter->name)); - } - result.text = lng_action_add_you(lt_from, textcmdLink(1, inviter->name)); - return result; -} diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 27fa5c233..d6bca0f95 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -20,7 +20,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -void historyInitMessages(); +void HistoryInitMessages(); +base::lambda HistoryDependentItemCallback(const FullMsgId &msgId); +MTPDmessage::Flags NewMessageFlags(gsl::not_null peer); class HistoryMessage : public HistoryItem, private HistoryItemInstantiated { public: @@ -30,7 +32,7 @@ public: static gsl::not_null create(gsl::not_null history, const MTPDmessageService &msg) { return _create(history, msg); } - static gsl::not_null create(gsl::not_null history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) { + static gsl::not_null create(gsl::not_null history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, gsl::not_null fwd) { return _create(history, msgId, flags, date, from, fwd); } static gsl::not_null create(gsl::not_null history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities) { @@ -64,9 +66,7 @@ public: return true; } bool displayEditedBadge(bool hasViaBotOrInlineMarkup) const; - bool uploading() const { - return _media && _media->uploading(); - } + bool uploading() const; void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const override; void setViewsCount(int32 count) override; @@ -84,14 +84,8 @@ public: TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; // ClickHandlerHost interface - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { - if (_media) _media->clickHandlerActiveChanged(p, active); - HistoryItem::clickHandlerActiveChanged(p, active); - } - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override { - if (_media) _media->clickHandlerPressedChanged(p, pressed); - HistoryItem::clickHandlerPressedChanged(p, pressed); - } + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; QString notificationHeader() const override; @@ -149,7 +143,7 @@ public: private: HistoryMessage(gsl::not_null history, const MTPDmessage &msg); HistoryMessage(gsl::not_null history, const MTPDmessageService &msg); - HistoryMessage(gsl::not_null history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd); // local forwarded + HistoryMessage(gsl::not_null history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, gsl::not_null fwd); // local forwarded HistoryMessage(gsl::not_null history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities); // local message HistoryMessage(gsl::not_null history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document HistoryMessage(gsl::not_null history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo @@ -167,16 +161,7 @@ private: int performResizeGetHeight(); void applyEditionToEmpty(); - bool displayForwardedFrom() const { - if (auto forwarded = Get()) { - return Has() - || !_media - || !_media->isDisplayed() - || !_media->hideForwardedFrom() - || forwarded->_authorOriginal->isChannel(); - } - return false; - } + bool displayForwardedFrom() const; 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; @@ -236,163 +221,3 @@ private: void updateMediaInBubbleState(); }; - -inline MTPDmessage::Flags newMessageFlags(PeerData *p) { - MTPDmessage::Flags result = 0; - if (!p->isSelf()) { - result |= MTPDmessage::Flag::f_out; - //if (p->isChat() || (p->isUser() && !p->asUser()->botInfo)) { - // result |= MTPDmessage::Flag::f_unread; - //} - } - return result; -} - -struct HistoryServiceDependentData { - MsgId msgId = 0; - HistoryItem *msg = nullptr; - ClickHandlerPtr lnk; -}; - -struct HistoryServicePinned : public RuntimeComponent, public HistoryServiceDependentData { -}; - -struct HistoryServiceGameScore : public RuntimeComponent, public HistoryServiceDependentData { - int score = 0; -}; - -struct HistoryServicePayment : public RuntimeComponent, public HistoryServiceDependentData { - QString amount; -}; - -namespace HistoryLayout { -class ServiceMessagePainter; -} // namespace HistoryLayout - -class HistoryService : public HistoryItem, private HistoryItemInstantiated { -public: - struct PreparedText { - QString text; - QList links; - }; - - static gsl::not_null create(gsl::not_null history, const MTPDmessageService &message) { - return _create(history, message); - } - static gsl::not_null create(gsl::not_null history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = nullptr) { - return _create(history, msgId, date, message, flags, from, photo); - } - - bool updateDependencyItem() override; - MsgId dependencyMsgId() const override { - if (auto dependent = GetDependentData()) { - return dependent->msgId; - } - return 0; - } - bool notificationReady() const override { - if (auto dependent = GetDependentData()) { - return (dependent->msg || !dependent->msgId); - } - return true; - } - - QRect countGeometry() const; - - void draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms) const override; - bool hasPoint(QPoint point) const override; - HistoryTextState getState(QPoint point, HistoryStateRequest request) const override; - - TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { - return _text.adjustSelection(selection, type); - } - - void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { - if (_media) _media->clickHandlerActiveChanged(p, active); - HistoryItem::clickHandlerActiveChanged(p, active); - } - void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override { - if (_media) _media->clickHandlerPressedChanged(p, pressed); - HistoryItem::clickHandlerPressedChanged(p, pressed); - } - - void applyEdition(const MTPDmessageService &message) override; - - int32 addToOverview(AddToOverviewMethod method) override; - void eraseFromOverview() override; - - bool needCheck() const override { - return false; - } - bool serviceMsg() const override { - return true; - } - TextWithEntities selectedText(TextSelection selection) const override; - QString inDialogsText() const override; - QString inReplyText() const override; - - ~HistoryService(); - -protected: - friend class HistoryLayout::ServiceMessagePainter; - - HistoryService(gsl::not_null history, const MTPDmessageService &message); - HistoryService(gsl::not_null history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = 0); - friend class HistoryItemInstantiated; - - void initDimensions() override; - int resizeContentGetHeight() override; - - void setServiceText(const PreparedText &prepared); - - QString fromLinkText() const { - return textcmdLink(1, _from->name); - }; - ClickHandlerPtr fromLink() const { - return peerOpenClickHandler(_from); - }; - - void removeMedia(); - -private: - HistoryServiceDependentData *GetDependentData() { - if (auto pinned = Get()) { - return pinned; - } else if (auto gamescore = Get()) { - return gamescore; - } else if (auto payment = Get()) { - return payment; - } - return nullptr; - } - const HistoryServiceDependentData *GetDependentData() const { - return const_cast(this)->GetDependentData(); - } - bool updateDependent(bool force = false); - void updateDependentText(); - void clearDependency(); - - void createFromMtp(const MTPDmessageService &message); - void setMessageByAction(const MTPmessageAction &action); - - PreparedText preparePinnedText(); - PreparedText prepareGameScoreText(); - PreparedText preparePaymentSentText(); - -}; - -class HistoryJoined : public HistoryService, private HistoryItemInstantiated { -public: - static gsl::not_null create(gsl::not_null history, const QDateTime &inviteDate, gsl::not_null inviter, MTPDmessage::Flags flags) { - return _create(history, inviteDate, inviter, flags); - } - -protected: - HistoryJoined(gsl::not_null history, const QDateTime &inviteDate, gsl::not_null inviter, MTPDmessage::Flags flags); - using HistoryItemInstantiated::_create; - friend class HistoryItemInstantiated; - -private: - static PreparedText GenerateText(gsl::not_null history, gsl::not_null inviter); - -}; diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp new file mode 100644 index 000000000..fc20aa2a1 --- /dev/null +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -0,0 +1,696 @@ +/* +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-2017 John Preston, https://desktop.telegram.org +*/ +#include "history/history_service.h" + +#include "lang/lang_keys.h" +#include "mainwidget.h" +#include "apiwrap.h" +#include "history/history_service_layout.h" +#include "history/history_media_types.h" +#include "history/history_message.h" +#include "auth_session.h" +#include "window/notifications_manager.h" + +namespace { + +constexpr auto kPinnedMessageTextLimit = 16; + +} // namespace + +TextParseOptions _historySrvOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags/* | TextParseMultiline*/ | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // lang-dependent +}; + +void HistoryService::setMessageByAction(const MTPmessageAction &action) { + auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) { + auto result = PreparedText {}; + auto &users = action.vusers.v; + if (users.size() == 1) { + auto u = App::user(peerFromUser(users[0])); + if (u == _from) { + result.links.push_back(fromLink()); + result.text = lng_action_user_joined(lt_from, fromLinkText()); + } else { + result.links.push_back(fromLink()); + result.links.push_back(peerOpenClickHandler(u)); + result.text = lng_action_add_user(lt_from, fromLinkText(), lt_user, textcmdLink(2, u->name)); + } + } else if (users.isEmpty()) { + result.links.push_back(fromLink()); + result.text = lng_action_add_user(lt_from, fromLinkText(), lt_user, "somebody"); + } else { + result.links.push_back(fromLink()); + for (auto i = 0, l = users.size(); i != l; ++i) { + auto user = App::user(peerFromUser(users[i])); + result.links.push_back(peerOpenClickHandler(user)); + + auto linkText = textcmdLink(i + 2, user->name); + if (i == 0) { + result.text = linkText; + } else if (i + 1 == l) { + result.text = lng_action_add_users_and_last(lt_accumulated, result.text, lt_user, linkText); + } else { + result.text = lng_action_add_users_and_one(lt_accumulated, result.text, lt_user, linkText); + } + } + result.text = lng_action_add_users_many(lt_from, fromLinkText(), lt_users, result.text); + } + return result; + }; + + auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) { + auto result = PreparedText {}; + result.links.push_back(fromLink()); + result.text = lng_action_user_joined_by_link(lt_from, fromLinkText()); + return result; + }; + + auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) { + auto result = PreparedText {}; + result.links.push_back(fromLink()); + result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle))); + return result; + }; + + auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) { + auto result = PreparedText {}; + if (isPost()) { + result.text = lang(lng_action_created_channel); + } else { + result.links.push_back(fromLink()); + result.text = lng_action_created_chat(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle))); + } + return result; + }; + + auto prepareChatDeletePhoto = [this] { + auto result = PreparedText {}; + if (isPost()) { + result.text = lang(lng_action_removed_photo_channel); + } else { + result.links.push_back(fromLink()); + result.text = lng_action_removed_photo(lt_from, fromLinkText()); + } + return result; + }; + + auto prepareChatDeleteUser = [this](const MTPDmessageActionChatDeleteUser &action) { + auto result = PreparedText {}; + if (peerFromUser(action.vuser_id) == _from->id) { + result.links.push_back(fromLink()); + result.text = lng_action_user_left(lt_from, fromLinkText()); + } else { + auto user = App::user(peerFromUser(action.vuser_id)); + result.links.push_back(fromLink()); + result.links.push_back(peerOpenClickHandler(user)); + result.text = lng_action_kick_user(lt_from, fromLinkText(), lt_user, textcmdLink(2, user->name)); + } + return result; + }; + + auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) { + auto result = PreparedText {}; + if (isPost()) { + result.text = lang(lng_action_changed_photo_channel); + } else { + result.links.push_back(fromLink()); + result.text = lng_action_changed_photo(lt_from, fromLinkText()); + } + return result; + }; + + auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) { + auto result = PreparedText {}; + if (isPost()) { + result.text = lng_action_changed_title_channel(lt_title, textClean(qs(action.vtitle))); + } else { + result.links.push_back(fromLink()); + result.text = lng_action_changed_title(lt_from, fromLinkText(), lt_title, textClean(qs(action.vtitle))); + } + return result; + }; + + auto messageText = PreparedText {}; + + switch (action.type()) { + case mtpc_messageActionChatAddUser: messageText = prepareChatAddUserText(action.c_messageActionChatAddUser()); break; + case mtpc_messageActionChatJoinedByLink: messageText = prepareChatJoinedByLink(action.c_messageActionChatJoinedByLink()); break; + case mtpc_messageActionChatCreate: messageText = prepareChatCreate(action.c_messageActionChatCreate()); break; + case mtpc_messageActionChannelCreate: messageText = prepareChannelCreate(action.c_messageActionChannelCreate()); break; + case mtpc_messageActionHistoryClear: break; // Leave empty text. + case mtpc_messageActionChatDeletePhoto: messageText = prepareChatDeletePhoto(); break; + case mtpc_messageActionChatDeleteUser: messageText = prepareChatDeleteUser(action.c_messageActionChatDeleteUser()); break; + case mtpc_messageActionChatEditPhoto: messageText = prepareChatEditPhoto(action.c_messageActionChatEditPhoto()); break; + case mtpc_messageActionChatEditTitle: messageText = prepareChatEditTitle(action.c_messageActionChatEditTitle()); break; + case mtpc_messageActionChatMigrateTo: messageText.text = lang(lng_action_group_migrate); break; + case mtpc_messageActionChannelMigrateFrom: messageText.text = lang(lng_action_group_migrate); break; + case mtpc_messageActionPinMessage: messageText = preparePinnedText(); break; + case mtpc_messageActionGameScore: messageText = prepareGameScoreText(); break; + case mtpc_messageActionPhoneCall: Unexpected("PhoneCall type in HistoryService."); + case mtpc_messageActionPaymentSent: messageText = preparePaymentSentText(); break; + default: messageText.text = lang(lng_message_empty); break; + } + + setServiceText(messageText); + + // Additional information. + switch (action.type()) { + case mtpc_messageActionChatAddUser: { + if (auto channel = history()->peer->asMegagroup()) { + auto &users = action.c_messageActionChatAddUser().vusers; + for_const (auto &item, users.v) { + if (item.v == AuthSession::CurrentUserId()) { + channel->mgInfo->joinedMessageFound = true; + break; + } + } + } + } break; + + case mtpc_messageActionChatJoinedByLink: { + if (_from->isSelf() && history()->peer->isMegagroup()) { + history()->peer->asChannel()->mgInfo->joinedMessageFound = true; + } + } break; + + case mtpc_messageActionChatEditPhoto: { + auto &photo = action.c_messageActionChatEditPhoto().vphoto; + if (photo.type() == mtpc_photo) { + _media = std::make_unique(this, history()->peer, photo.c_photo(), st::msgServicePhotoWidth); + } + } break; + + case mtpc_messageActionChatMigrateTo: + case mtpc_messageActionChannelMigrateFrom: { + _flags |= MTPDmessage_ClientFlag::f_is_group_migrate; + } break; + } +} + +bool HistoryService::updateDependent(bool force) { + auto dependent = GetDependentData(); + t_assert(dependent != nullptr); + + if (!force) { + if (!dependent->msgId || dependent->msg) { + return true; + } + } + + if (!dependent->lnk) { + dependent->lnk = goToMessageClickHandler(history()->peer, dependent->msgId); + } + bool gotDependencyItem = false; + if (!dependent->msg) { + dependent->msg = App::histItemById(channelId(), dependent->msgId); + if (dependent->msg) { + App::historyRegDependency(this, dependent->msg); + gotDependencyItem = true; + } + } + if (dependent->msg) { + updateDependentText(); + } else if (force) { + if (dependent->msgId > 0) { + dependent->msgId = 0; + gotDependencyItem = true; + } + updateDependentText(); + } + if (force && gotDependencyItem) { + AuthSession::Current().notifications().checkDelayed(); + } + return (dependent->msg || !dependent->msgId); +} + +HistoryService::PreparedText HistoryService::preparePinnedText() { + auto result = PreparedText {}; + auto pinned = Get(); + if (pinned && pinned->msg) { + auto mediaText = ([pinned]() -> QString { + auto media = pinned->msg->getMedia(); + switch (media ? media->type() : MediaTypeCount) { + case MediaTypePhoto: return lang(lng_action_pinned_media_photo); + case MediaTypeVideo: return lang(lng_action_pinned_media_video); + case MediaTypeContact: return lang(lng_action_pinned_media_contact); + case MediaTypeFile: return lang(lng_action_pinned_media_file); + case MediaTypeGif: { + if (auto document = media->getDocument()) { + if (document->isRoundVideo()) { + return lang(lng_action_pinned_media_video_message); + } + } + return lang(lng_action_pinned_media_gif); + } break; + case MediaTypeSticker: { + auto emoji = static_cast(media)->emoji(); + if (emoji.isEmpty()) { + return lang(lng_action_pinned_media_sticker); + } + return lng_action_pinned_media_emoji_sticker(lt_emoji, emoji); + } break; + case MediaTypeLocation: return lang(lng_action_pinned_media_location); + case MediaTypeMusicFile: return lang(lng_action_pinned_media_audio); + case MediaTypeVoiceFile: return lang(lng_action_pinned_media_voice); + case MediaTypeGame: { + auto title = static_cast(media)->game()->title; + return lng_action_pinned_media_game(lt_game, title); + } break; + } + return QString(); + })(); + + result.links.push_back(fromLink()); + result.links.push_back(pinned->lnk); + if (mediaText.isEmpty()) { + auto original = pinned->msg->originalText().text; + auto cutAt = 0; + auto limit = kPinnedMessageTextLimit; + auto size = original.size(); + for (; limit != 0;) { + --limit; + if (cutAt >= size) break; + if (original.at(cutAt).isLowSurrogate() && cutAt + 1 < size && original.at(cutAt + 1).isHighSurrogate()) { + cutAt += 2; + } else { + ++cutAt; + } + } + if (!limit && cutAt + 5 < size) { + original = original.mid(0, cutAt) + qstr("..."); + } + result.text = lng_action_pinned_message(lt_from, fromLinkText(), lt_text, textcmdLink(2, original)); + } else { + result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, textcmdLink(2, mediaText)); + } + } else if (pinned && pinned->msgId) { + result.links.push_back(fromLink()); + result.links.push_back(pinned->lnk); + result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, textcmdLink(2, lang(lng_contacts_loading))); + } else { + result.links.push_back(fromLink()); + result.text = lng_action_pinned_media(lt_from, fromLinkText(), lt_media, lang(lng_deleted_message)); + } + return result; +} + +HistoryService::PreparedText HistoryService::prepareGameScoreText() { + auto result = PreparedText {}; + auto gamescore = Get(); + + auto computeGameTitle = [gamescore, &result]() -> QString { + if (gamescore && gamescore->msg) { + if (auto media = gamescore->msg->getMedia()) { + if (media->type() == MediaTypeGame) { + result.links.push_back(MakeShared(gamescore->msg, 0, 0)); + auto titleText = static_cast(media)->game()->title; + return textcmdLink(result.links.size(), titleText); + } + } + return lang(lng_deleted_message); + } else if (gamescore && gamescore->msgId) { + return lang(lng_contacts_loading); + } + return QString(); + }; + + auto scoreNumber = gamescore ? gamescore->score : 0; + if (_from->isSelf()) { + auto gameTitle = computeGameTitle(); + if (gameTitle.isEmpty()) { + result.text = lng_action_game_you_scored_no_game(lt_count, scoreNumber); + } else { + result.text = lng_action_game_you_scored(lt_count, scoreNumber, lt_game, gameTitle); + } + } else { + result.links.push_back(fromLink()); + auto gameTitle = computeGameTitle(); + if (gameTitle.isEmpty()) { + result.text = lng_action_game_score_no_game(lt_count, scoreNumber, lt_from, fromLinkText()); + } else { + result.text = lng_action_game_score(lt_count, scoreNumber, lt_from, fromLinkText(), lt_game, gameTitle); + } + } + return result; +} + +HistoryService::PreparedText HistoryService::preparePaymentSentText() { + auto result = PreparedText {}; + auto payment = Get(); + + auto invoiceTitle = ([payment]() -> QString { + if (payment && payment->msg) { + if (auto media = payment->msg->getMedia()) { + if (media->type() == MediaTypeInvoice) { + return static_cast(media)->getTitle(); + } + } + return lang(lng_deleted_message); + } else if (payment && payment->msgId) { + return lang(lng_contacts_loading); + } + return QString(); + })(); + + if (invoiceTitle.isEmpty()) { + result.text = lng_action_payment_done(lt_amount, payment->amount, lt_user, history()->peer->name); + } else { + result.text = lng_action_payment_done_for(lt_amount, payment->amount, lt_user, history()->peer->name, lt_invoice, invoiceTitle); + } + return result; +} + +HistoryService::HistoryService(gsl::not_null history, const MTPDmessageService &message) : + HistoryItem(history, message.vid.v, mtpCastFlags(message.vflags.v), ::date(message.vdate), message.has_from_id() ? message.vfrom_id.v : 0) { + createFromMtp(message); + setMessageByAction(message.vaction); +} + +HistoryService::HistoryService(gsl::not_null history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from, PhotoData *photo) : + HistoryItem(history, msgId, flags, date, from) { + setServiceText(message); + if (photo) { + _media = std::make_unique(this, history->peer, photo, st::msgServicePhotoWidth); + } +} + +void HistoryService::initDimensions() { + _maxw = _text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); + _minh = _text.minHeight(); + if (_media) { + _media->initDimensions(); + } +} + +bool HistoryService::updateDependencyItem() { + if (GetDependentData()) { + return updateDependent(true); + } + return HistoryItem::updateDependencyItem(); +} + +QRect HistoryService::countGeometry() const { + auto result = QRect(0, 0, width(), _height); + if (Adaptive::ChatWide()) { + result.setWidth(qMin(result.width(), st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); + } + return result.marginsRemoved(st::msgServiceMargin); +} + +TextWithEntities HistoryService::selectedText(TextSelection selection) const { + return _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection); +} + +QString HistoryService::inDialogsText() const { + return textcmdLink(1, textClean(notificationText())); +} + +QString HistoryService::inReplyText() const { + QString result = HistoryService::notificationText(); + return result.trimmed().startsWith(author()->name) ? result.trimmed().mid(author()->name.size()).trimmed() : result; +} + +void HistoryService::setServiceText(const PreparedText &prepared) { + _text.setText(st::serviceTextStyle, prepared.text, _historySrvOptions); + auto linkIndex = 0; + for_const (auto &link, prepared.links) { + // Link indices start with 1. + _text.setLink(++linkIndex, link); + } + + setPendingInitDimensions(); + _textWidth = -1; + _textHeight = 0; +} + +void HistoryService::draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms) const { + auto height = _height - st::msgServiceMargin.top() - st::msgServiceMargin.bottom(); + auto dateh = 0; + auto unreadbarh = 0; + if (auto date = Get()) { + dateh = date->height(); + p.translate(0, dateh); + clip.translate(0, -dateh); + height -= dateh; + } + if (auto unreadbar = Get()) { + unreadbarh = unreadbar->height(); + if (clip.intersects(QRect(0, 0, width(), unreadbarh))) { + unreadbar->paint(p, 0, width()); + } + p.translate(0, unreadbarh); + clip.translate(0, -unreadbarh); + height -= unreadbarh; + } + + HistoryLayout::PaintContext context(ms, clip, selection); + HistoryLayout::ServiceMessagePainter::paint(p, this, context, height); + + if (auto skiph = dateh + unreadbarh) { + p.translate(0, -skiph); + } +} + +int HistoryService::resizeContentGetHeight() { + _height = displayedDateHeight(); + if (auto unreadbar = Get()) { + _height += unreadbar->height(); + } + + if (_text.isEmpty()) { + _textHeight = 0; + } else { + auto contentWidth = width(); + if (Adaptive::ChatWide()) { + accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); + } + contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins + if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) { + contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1; + } + + auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0); + if (nwidth != _textWidth) { + _textWidth = nwidth; + _textHeight = _text.countHeight(nwidth); + } + if (contentWidth >= _maxw) { + _height += _minh; + } else { + _height += _textHeight; + } + _height += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); + if (_media) { + _height += st::msgServiceMargin.top() + _media->resizeGetHeight(_media->currentWidth()); + } + } + + return _height; +} + +bool HistoryService::hasPoint(QPoint point) const { + auto g = countGeometry(); + if (g.width() < 1) { + return false; + } + + if (auto dateh = displayedDateHeight()) { + g.setTop(g.top() + dateh); + } + if (auto unreadbar = Get()) { + g.setTop(g.top() + unreadbar->height()); + } + if (_media) { + g.setHeight(g.height() - (st::msgServiceMargin.top() + _media->height())); + } + return g.contains(point); +} + +HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest request) const { + HistoryTextState result; + + auto g = countGeometry(); + if (g.width() < 1) { + return result; + } + + if (auto dateh = displayedDateHeight()) { + point.setY(point.y() - dateh); + g.setHeight(g.height() - dateh); + } + if (auto unreadbar = Get()) { + auto unreadbarh = unreadbar->height(); + point.setY(point.y() - unreadbarh); + g.setHeight(g.height() - unreadbarh); + } + + if (_media) { + g.setHeight(g.height() - (st::msgServiceMargin.top() + _media->height())); + } + auto trect = g.marginsAdded(-st::msgServicePadding); + if (trect.contains(point)) { + auto textRequest = request.forText(); + textRequest.align = style::al_center; + result = _text.getState(point - trect.topLeft(), trect.width(), textRequest); + if (auto gamescore = Get()) { + if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) { + result.link = gamescore->lnk; + } + } else if (auto payment = Get()) { + if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) { + result.link = payment->lnk; + } + } + } else if (_media) { + result = _media->getState(point - QPoint(st::msgServiceMargin.left() + (g.width() - _media->maxWidth()) / 2, st::msgServiceMargin.top() + g.height() + st::msgServiceMargin.top()), request); + } + return result; +} + +void HistoryService::createFromMtp(const MTPDmessageService &message) { + if (message.vaction.type() == mtpc_messageActionGameScore) { + UpdateComponents(HistoryServiceGameScore::Bit()); + Get()->score = message.vaction.c_messageActionGameScore().vscore.v; + } else if (message.vaction.type() == mtpc_messageActionPaymentSent) { + UpdateComponents(HistoryServicePayment::Bit()); + auto amount = message.vaction.c_messageActionPaymentSent().vtotal_amount.v; + auto currency = qs(message.vaction.c_messageActionPaymentSent().vcurrency); + Get()->amount = HistoryInvoice::fillAmountAndCurrency(amount, currency); + } + if (message.has_reply_to_msg_id()) { + if (message.vaction.type() == mtpc_messageActionPinMessage) { + UpdateComponents(HistoryServicePinned::Bit()); + } + if (auto dependent = GetDependentData()) { + dependent->msgId = message.vreply_to_msg_id.v; + if (!updateDependent() && App::api()) { + App::api()->requestMessageData(history()->peer->asChannel(), dependent->msgId, HistoryDependentItemCallback(fullId())); + } + } + } + setMessageByAction(message.vaction); +} + +void HistoryService::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (_media) _media->clickHandlerActiveChanged(p, active); + HistoryItem::clickHandlerActiveChanged(p, active); +} + +void HistoryService::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (_media) _media->clickHandlerPressedChanged(p, pressed); + HistoryItem::clickHandlerPressedChanged(p, pressed); +} + +void HistoryService::applyEdition(const MTPDmessageService &message) { + clearDependency(); + UpdateComponents(0); + + createFromMtp(message); + + if (message.vaction.type() == mtpc_messageActionHistoryClear) { + removeMedia(); + finishEditionToEmpty(); + } else { + finishEdition(-1); + } +} + +void HistoryService::removeMedia() { + if (!_media) return; + + bool mediaWasDisplayed = _media->isDisplayed(); + _media.reset(); + if (mediaWasDisplayed) { + _textWidth = -1; + _textHeight = 0; + } +} + +int32 HistoryService::addToOverview(AddToOverviewMethod method) { + if (!indexInOverview()) return 0; + + int32 result = 0; + if (auto media = getMedia()) { + result |= media->addToOverview(method); + } + return result; +} + +void HistoryService::eraseFromOverview() { + if (auto media = getMedia()) { + media->eraseFromOverview(); + } +} + +void HistoryService::updateDependentText() { + auto text = PreparedText {}; + if (Has()) { + text = preparePinnedText(); + } else if (Has()) { + text = prepareGameScoreText(); + } else if (Has()) { + text = preparePaymentSentText(); + } else { + return; + } + + setServiceText(text); + if (history()->textCachedFor == this) { + history()->textCachedFor = nullptr; + } + if (App::main()) { + App::main()->dlgUpdated(history()->peer, id); + } + App::historyUpdateDependent(this); +} + +void HistoryService::clearDependency() { + if (auto dependent = GetDependentData()) { + if (dependent->msg) { + App::historyUnregDependency(this, dependent->msg); + } + } +} + +HistoryService::~HistoryService() { + clearDependency(); + _media.reset(); +} + +HistoryJoined::HistoryJoined(gsl::not_null history, const QDateTime &inviteDate, gsl::not_null inviter, MTPDmessage::Flags flags) + : HistoryService(history, clientMsgId(), inviteDate, GenerateText(history, inviter), flags) { +} + +HistoryJoined::PreparedText HistoryJoined::GenerateText(gsl::not_null history, gsl::not_null inviter) { + if (inviter->id == AuthSession::CurrentUserPeerId()) { + return { lang(history->isMegagroup() ? lng_action_you_joined_group : lng_action_you_joined) }; + } + auto result = PreparedText {}; + result.links.push_back(peerOpenClickHandler(inviter)); + if (history->isMegagroup()) { + result.text = lng_action_add_you_group(lt_from, textcmdLink(1, inviter->name)); + } + result.text = lng_action_add_you(lt_from, textcmdLink(1, inviter->name)); + return result; +} diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h new file mode 100644 index 000000000..914098852 --- /dev/null +++ b/Telegram/SourceFiles/history/history_service.h @@ -0,0 +1,166 @@ +/* +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-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +struct HistoryServiceDependentData { + MsgId msgId = 0; + HistoryItem *msg = nullptr; + ClickHandlerPtr lnk; +}; + +struct HistoryServicePinned : public RuntimeComponent, public HistoryServiceDependentData { +}; + +struct HistoryServiceGameScore : public RuntimeComponent, public HistoryServiceDependentData { + int score = 0; +}; + +struct HistoryServicePayment : public RuntimeComponent, public HistoryServiceDependentData { + QString amount; +}; + +namespace HistoryLayout { +class ServiceMessagePainter; +} // namespace HistoryLayout + +class HistoryService : public HistoryItem, private HistoryItemInstantiated { +public: + struct PreparedText { + QString text; + QList links; + }; + + static gsl::not_null create(gsl::not_null history, const MTPDmessageService &message) { + return _create(history, message); + } + static gsl::not_null create(gsl::not_null history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = nullptr) { + return _create(history, msgId, date, message, flags, from, photo); + } + + bool updateDependencyItem() override; + MsgId dependencyMsgId() const override { + if (auto dependent = GetDependentData()) { + return dependent->msgId; + } + return 0; + } + bool notificationReady() const override { + if (auto dependent = GetDependentData()) { + return (dependent->msg || !dependent->msgId); + } + return true; + } + + QRect countGeometry() const; + + void draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms) const override; + bool hasPoint(QPoint point) const override; + HistoryTextState getState(QPoint point, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { + return _text.adjustSelection(selection, type); + } + + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + + void applyEdition(const MTPDmessageService &message) override; + + int32 addToOverview(AddToOverviewMethod method) override; + void eraseFromOverview() override; + + bool needCheck() const override { + return false; + } + bool serviceMsg() const override { + return true; + } + TextWithEntities selectedText(TextSelection selection) const override; + QString inDialogsText() const override; + QString inReplyText() const override; + + ~HistoryService(); + +protected: + friend class HistoryLayout::ServiceMessagePainter; + + HistoryService(gsl::not_null history, const MTPDmessageService &message); + HistoryService(gsl::not_null history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = 0); + friend class HistoryItemInstantiated; + + void initDimensions() override; + int resizeContentGetHeight() override; + + void setServiceText(const PreparedText &prepared); + + QString fromLinkText() const { + return textcmdLink(1, _from->name); + }; + ClickHandlerPtr fromLink() const { + return peerOpenClickHandler(_from); + }; + + void removeMedia(); + +private: + HistoryServiceDependentData *GetDependentData() { + if (auto pinned = Get()) { + return pinned; + } else if (auto gamescore = Get()) { + return gamescore; + } else if (auto payment = Get()) { + return payment; + } + return nullptr; + } + const HistoryServiceDependentData *GetDependentData() const { + return const_cast(this)->GetDependentData(); + } + bool updateDependent(bool force = false); + void updateDependentText(); + void clearDependency(); + + void createFromMtp(const MTPDmessageService &message); + void setMessageByAction(const MTPmessageAction &action); + + PreparedText preparePinnedText(); + PreparedText prepareGameScoreText(); + PreparedText preparePaymentSentText(); + +}; + +class HistoryJoined : public HistoryService, private HistoryItemInstantiated { +public: + static gsl::not_null create(gsl::not_null history, const QDateTime &inviteDate, gsl::not_null inviter, MTPDmessage::Flags flags) { + return _create(history, inviteDate, inviter, flags); + } + +protected: + HistoryJoined(gsl::not_null history, const QDateTime &inviteDate, gsl::not_null inviter, MTPDmessage::Flags flags); + using HistoryItemInstantiated::_create; + friend class HistoryItemInstantiated; + +private: + static PreparedText GenerateText(gsl::not_null history, gsl::not_null inviter); + +}; + +extern TextParseOptions _historySrvOptions; diff --git a/Telegram/SourceFiles/history/history_service_layout.cpp b/Telegram/SourceFiles/history/history_service_layout.cpp index 522ef3744..d60ce5ed5 100644 --- a/Telegram/SourceFiles/history/history_service_layout.cpp +++ b/Telegram/SourceFiles/history/history_service_layout.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "history/history_service_layout.h" +#include "history/history_service.h" +#include "history/history_media.h" #include "data/data_abstract_structure.h" #include "styles/style_history.h" #include "mainwidget.h" diff --git a/Telegram/SourceFiles/history/history_service_layout.h b/Telegram/SourceFiles/history/history_service_layout.h index f7152c8a7..55b3bbf2a 100644 --- a/Telegram/SourceFiles/history/history_service_layout.h +++ b/Telegram/SourceFiles/history/history_service_layout.h @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once +class HistoryService; + namespace HistoryLayout { int WideChatWidth(); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 0cc7c018c..5f9395aae 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -39,6 +39,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/effects/ripple_animation.h" #include "inline_bots/inline_bot_result.h" #include "data/data_drafts.h" +#include "history/history_message.h" #include "history/history_service_layout.h" #include "history/history_media_types.h" #include "history/history_drag_area.h" @@ -3097,8 +3098,8 @@ void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const App::main()->readServerHistory(history); fastShowAtEnd(history); - PeerData *p = App::peer(peer); - auto flags = newMessageFlags(p) | MTPDmessage::Flag::f_media; // unread, out + auto p = App::peer(peer); + auto flags = NewMessageFlags(p) | MTPDmessage::Flag::f_media; // unread, out bool lastKeyboardUsed = lastForceReplyReplied(FullMsgId(peerToChannel(peer), replyTo)); @@ -4545,7 +4546,7 @@ void HistoryWidget::sendFileConfirmed(const FileLoadResultPtr &file) { fastShowAtEnd(h); - auto flags = newMessageFlags(h->peer) | MTPDmessage::Flag::f_media; // unread, out + auto flags = NewMessageFlags(h->peer) | MTPDmessage::Flag::f_media; // unread, out if (file->to.replyTo) flags |= MTPDmessage::Flag::f_reply_to_msg_id; bool channelPost = h->peer->isChannel() && !h->peer->isMegagroup(); bool showFromName = !channelPost || h->peer->asChannel()->addsSignature(); @@ -5358,7 +5359,7 @@ void HistoryWidget::onInlineResultSend(InlineBots::Result *result, UserData *bot bool lastKeyboardUsed = lastForceReplyReplied(); bool out = !_peer->isSelf(), unread = !_peer->isSelf(); - auto flags = newMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out + auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out auto sendFlags = qFlags(MTPmessages_SendInlineBotResult::Flag::f_clear_draft); if (replyToId()) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; @@ -5531,7 +5532,7 @@ bool HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &capti bool lastKeyboardUsed = lastForceReplyReplied(); bool out = !_peer->isSelf(), unread = !_peer->isSelf(); - auto flags = newMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out + auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out auto sendFlags = MTPmessages_SendMedia::Flags(0); if (replyToId()) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; @@ -5587,7 +5588,7 @@ void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption) bool lastKeyboardUsed = lastForceReplyReplied(); bool out = !_peer->isSelf(), unread = !_peer->isSelf(); - auto flags = newMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out + auto flags = NewMessageFlags(_peer) | MTPDmessage::Flag::f_media; // unread, out auto sendFlags = MTPmessages_SendMedia::Flags(0); if (replyToId()) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 07ad694bf..21e972996 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -35,6 +35,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "dialogswidget.h" #include "historywidget.h" +#include "history/history_message.h" +#include "history/history_media.h" #include "history/history_service_layout.h" #include "overviewwidget.h" #include "lang/lang_keys.h" @@ -1493,7 +1495,7 @@ void MainWidget::sendMessage(const MessageToSend &message) { App::historyRegSentData(randomId, history->peer->id, sendingText); MTPstring msgText(MTP_string(sendingText)); - auto flags = newMessageFlags(history->peer) | MTPDmessage::Flag::f_entities; // unread, out + auto flags = NewMessageFlags(history->peer) | MTPDmessage::Flag::f_entities; // unread, out auto sendFlags = MTPmessages_SendMessage::Flags(0); if (replyTo) { flags |= MTPDmessage::Flag::f_reply_to_msg_id; diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp index f8590854c..1836df143 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.cpp +++ b/Telegram/SourceFiles/media/player/media_player_float.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "media/player/media_player_float.h" #include "styles/style_media_player.h" +#include "history/history_media.h" #include "media/media_clip_reader.h" #include "media/view/media_clip_playback.h" #include "media/media_audio.h" diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 2de23f803..9efcb75b7 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "messenger.h" #include "auth_session.h" #include "calls/calls_instance.h" +#include "history/history_media.h" namespace Media { namespace Player { diff --git a/Telegram/SourceFiles/media/player/media_player_list.cpp b/Telegram/SourceFiles/media/player/media_player_list.cpp index 2d87d3190..a4e915d38 100644 --- a/Telegram/SourceFiles/media/player/media_player_list.cpp +++ b/Telegram/SourceFiles/media/player/media_player_list.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "media/player/media_player_instance.h" #include "overview/overview_layout.h" #include "styles/style_media_player.h" +#include "history/history_media.h" namespace Media { namespace Player { diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 25c45e8ed..7f70138b4 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -32,6 +32,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_mediaview.h" #include "styles/style_history.h" #include "media/media_audio.h" +#include "history/history_message.h" #include "history/history_media_types.h" #include "window/themes/window_theme_preview.h" #include "base/task_queue.h" @@ -1182,8 +1183,8 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { _zoom = 0; _caption = Text(); - if (HistoryMessage *itemMsg = item ? item->toHistoryMessage() : nullptr) { - if (HistoryPhoto *photoMsg = dynamic_cast(itemMsg->getMedia())) { + if (auto itemMsg = item ? item->toHistoryMessage() : nullptr) { + if (auto photoMsg = dynamic_cast(itemMsg->getMedia())) { _caption.setMarkedText(st::mediaviewCaptionStyle, photoMsg->getCaption(), (item->author()->isUser() && item->author()->asUser()->botInfo) ? _captionBotOptions : _captionTextOptions); } } diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index a270bec03..4a6e53abe 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -104,7 +104,7 @@ Messenger::Messenger() : QObject() style::startManager(); anim::startManager(); - historyInit(); + HistoryInit(); Media::Player::start(); DEBUG_LOG(("Application Info: inited...")); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index f716d32d8..6623f73c6 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -38,6 +38,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "overviewwidget.h" #include "application.h" #include "overview/overview_layout.h" +#include "history/history_message.h" #include "history/history_media_types.h" #include "history/history_service_layout.h" #include "media/media_audio.h" diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index d73156209..f8a5f54a7 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -161,6 +161,8 @@ <(src_loc)/history/history_media_types.h <(src_loc)/history/history_message.cpp <(src_loc)/history/history_message.h +<(src_loc)/history/history_service.cpp +<(src_loc)/history/history_service.h <(src_loc)/history/history_service_layout.cpp <(src_loc)/history/history_service_layout.h <(src_loc)/inline_bots/inline_bot_layout_internal.cpp