diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index fccc96f5ff..fbb7b6fdee 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -182,6 +182,16 @@ inFwdTextPaletteSelected: TextPalette(defaultTextPalette) { outFwdTextPaletteSelected: TextPalette(defaultTextPalette) { linkFg: msgOutServiceFgSelected; } +inSemiboldPalette: TextPalette(inTextPalette) { + linkFg: msgInServiceFg; + selectFg: msgInServiceFgSelected; + selectLinkFg: msgInServiceFgSelected; +} +outSemiboldPalette: TextPalette(outTextPalette) { + linkFg: msgOutServiceFg; + selectFg: msgOutServiceFgSelected; + selectLinkFg: msgOutServiceFgSelected; +} mediaPadding: margins(0px, 0px, 0px, 0px); mediaCaptionSkip: 5px; diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5ffb08af48..c3ca3752ca 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1102,6 +1102,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_theme_editor_title" = "Edit color palette"; "lng_theme_editor_export_button" = "Export theme"; +"lng_payments_not_supported" = "Sorry, Telegram Desktop doesn't support payments yet. Please use one of our mobile apps to do this."; + // Not used "lng_topbar_info" = "Info"; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index e17f980da4..5137d373c7 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -89,6 +89,10 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { } } break; + case ButtonType::Buy: { + Ui::show(Box(lang(lng_payments_not_supported))); + } break; + case ButtonType::Url: { auto url = QString::fromUtf8(button->data); auto skipConfirmation = false; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index fd12662894..54483da011 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -813,6 +813,8 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, default: badMedia = MediaCheckResult::Unsupported; break; } break; + case mtpc_messageMediaInvoice: + break; case mtpc_messageMediaUnsupported: default: badMedia = MediaCheckResult::Unsupported; break; } diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 1a45f866c4..a5ada2468d 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -118,6 +118,7 @@ enum HistoryMediaType { MediaTypeMusicFile, MediaTypeVoiceFile, MediaTypeGame, + MediaTypeInvoice, MediaTypeCount }; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index c4a0eabc5f..ce581b3f71 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -438,6 +438,10 @@ void HistoryMessageReplyMarkup::createFromButtonRows(const QVectordetachFromParent(); - delete _p; +HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr pointer) : _pointer(std::move(pointer)) { + if (_pointer) { + _pointer->attachToParent(); } - _p = p; - if (_p) { - _p->attachToParent(); +} + +HistoryMediaPtr &HistoryMediaPtr::operator=(std::unique_ptr pointer) { + if (_pointer) { + _pointer->detachFromParent(); } + _pointer = std::move(pointer); + if (_pointer) { + _pointer->attachToParent(); + } + return *this; } namespace internal { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 7de05fa5e8..b10c00eee5 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -215,6 +215,7 @@ struct HistoryMessageReplyMarkup : public RuntimeComponent other); + HistoryMediaPtr &operator=(std::unique_ptr other); + + HistoryMedia *get() const { + return _pointer.get(); } - void reset(HistoryMedia *p = nullptr); - void clear() { - reset(); + void reset(std::unique_ptr pointer = nullptr) { + *this = std::move(pointer); } bool isNull() const { - return data() == nullptr; + return !_pointer; } HistoryMedia *operator->() const { - return data(); + return get(); } HistoryMedia &operator*() const { t_assert(!isNull()); - return *data(); + return *get(); } explicit operator bool() const { return !isNull(); } ~HistoryMediaPtr() { - clear(); + reset(); } private: - HistoryMedia *_p = nullptr; + std::unique_ptr _pointer; }; @@ -726,7 +729,7 @@ public: } HistoryMedia *getMedia() const { - return _media.data(); + return _media.get(); } virtual void setText(const TextWithEntities &textWithEntities) { } diff --git a/Telegram/SourceFiles/history/history_media.h b/Telegram/SourceFiles/history/history_media.h index bde0e516ee..ed8036caaa 100644 --- a/Telegram/SourceFiles/history/history_media.h +++ b/Telegram/SourceFiles/history/history_media.h @@ -96,7 +96,7 @@ public: virtual bool uploading() const { return false; } - virtual HistoryMedia *clone(HistoryItem *newParent) const = 0; + virtual std::unique_ptr clone(HistoryItem *newParent) const = 0; virtual DocumentData *getDocument() { return nullptr; diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index 248962730a..2314a565d8 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -2530,7 +2530,9 @@ int32 articleThumbHeight(PhotoData *thumb, int32 width) { return qMax(thumb->medium->height() * width / thumb->medium->width(), 1); } -int32 _lineHeight = 0; +int unitedLineHeight() { + return qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); +} } // namespace @@ -2557,7 +2559,7 @@ void HistoryWebPage::initDimensions() { _maxw = _minh = _height = 0; return; } - if (!_lineHeight) _lineHeight = qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); + auto lineHeight = unitedLineHeight(); if (!_openl && !_data->url.isEmpty()) { _openl = MakeShared(_data->url, true); @@ -2631,14 +2633,14 @@ void HistoryWebPage::initDimensions() { _maxw = skipBlockWidth; _minh = 0; - int32 siteNameHeight = _data->siteName.isEmpty() ? 0 : _lineHeight; - int32 titleMinHeight = _title.isEmpty() ? 0 : _lineHeight; + int32 siteNameHeight = _data->siteName.isEmpty() ? 0 : lineHeight; + int32 titleMinHeight = _title.isEmpty() ? 0 : lineHeight; int32 descMaxLines = (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1)); - int32 descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * _lineHeight); + int32 descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); int32 articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight; int32 articlePhotoMaxWidth = 0; if (_asArticle) { - articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), _lineHeight); + articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), lineHeight); } if (_siteNameWidth) { @@ -2647,7 +2649,7 @@ void HistoryWebPage::initDimensions() { } else { accumulate_max(_maxw, _siteNameWidth + articlePhotoMaxWidth); } - _minh += _lineHeight; + _minh += lineHeight; } if (!_title.isEmpty()) { accumulate_max(_maxw, _title.maxWidth() + articlePhotoMaxWidth); @@ -2693,13 +2695,15 @@ int HistoryWebPage::resizeGetHeight(int width) { _width = width/* = qMin(width, _maxw)*/; width -= st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); - int32 linesMax = 5; - int32 siteNameLines = _siteNameWidth ? 1 : 0, siteNameHeight = _siteNameWidth ? _lineHeight : 0; + auto lineHeight = unitedLineHeight(); + auto linesMax = 5; + auto siteNameLines = _siteNameWidth ? 1 : 0; + auto siteNameHeight = _siteNameWidth ? lineHeight : 0; if (_asArticle) { - _pixh = linesMax * _lineHeight; + _pixh = linesMax * lineHeight; do { _pixw = articleThumbWidth(_data->photo, _pixh); - int32 wleft = width - st::webPagePhotoDelta - qMax(_pixw, int16(_lineHeight)); + int32 wleft = width - st::webPagePhotoDelta - qMax(_pixw, int16(lineHeight)); _height = siteNameHeight; @@ -2711,23 +2715,23 @@ int HistoryWebPage::resizeGetHeight(int width) { } else { _titleLines = 2; } - _height += _titleLines * _lineHeight; + _height += _titleLines * lineHeight; } - int32 descriptionHeight = _description.countHeight(wleft); + auto descriptionHeight = _description.countHeight(wleft); if (descriptionHeight < (linesMax - siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { _descriptionLines = (descriptionHeight / st::webPageDescriptionFont->height); } else { _descriptionLines = (linesMax - siteNameLines - _titleLines); } - _height += _descriptionLines * _lineHeight; + _height += _descriptionLines * lineHeight; if (_height >= _pixh) { break; } - _pixh -= _lineHeight; - } while (_pixh > _lineHeight); + _pixh -= lineHeight; + } while (_pixh > lineHeight); _height += bottomInfoPadding(); } else { _height = siteNameHeight; @@ -2740,7 +2744,7 @@ int HistoryWebPage::resizeGetHeight(int width) { } else { _titleLines = 2; } - _height += _titleLines * _lineHeight; + _height += _titleLines * lineHeight; } if (_description.isEmpty()) { @@ -2752,7 +2756,7 @@ int HistoryWebPage::resizeGetHeight(int width) { } else { _descriptionLines = (linesMax - siteNameLines - _titleLines); } - _height += _descriptionLines * _lineHeight; + _height += _descriptionLines * lineHeight; } if (_attach) { @@ -2797,11 +2801,12 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, T QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, _height - tshift - bshift, _width)); p.fillRect(bar, barfg); + auto lineHeight = unitedLineHeight(); if (_asArticle) { _data->photo->medium->load(false, false); bool full = _data->photo->medium->loaded(); QPixmap pix; - int32 pw = qMax(_pixw, int16(_lineHeight)), ph = _pixh; + int32 pw = qMax(_pixw, int16(lineHeight)), ph = _pixh; int32 pixw = _pixw, pixh = articleThumbHeight(_data->photo, _pixw); int32 maxw = convertScale(_data->photo->medium->width()), maxh = convertScale(_data->photo->medium->height()); if (pixw * ph != pixh * pw) { @@ -2824,7 +2829,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, T p.setFont(st::webPageTitleFont); p.setPen(semibold); p.drawTextLeft(padding.left(), tshift, _width, (width >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, width)); - tshift += _lineHeight; + tshift += lineHeight; } if (_titleLines) { p.setPen(outbg ? st::webPageTitleOutFg : st::webPageTitleInFg); @@ -2833,7 +2838,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, T endskip = _parent->skipBlockWidth(); } _title.drawLeftElided(p, padding.left(), tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); - tshift += _titleLines * _lineHeight; + tshift += _titleLines * lineHeight; } if (_descriptionLines) { p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); @@ -2842,7 +2847,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, T endskip = _parent->skipBlockWidth(); } _description.drawLeftElided(p, padding.left(), tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); - tshift += _descriptionLines * _lineHeight; + tshift += _descriptionLines * lineHeight; } if (_attach) { auto attachAtTop = !_siteNameWidth && !_titleLines && !_descriptionLines; @@ -2899,9 +2904,10 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ } width -= padding.left() + padding.right(); - bool inThumb = false; + auto lineHeight = unitedLineHeight(); + auto inThumb = false; if (_asArticle) { - int32 pw = qMax(_pixw, int16(_lineHeight)); + int32 pw = qMax(_pixw, int16(lineHeight)); if (rtlrect(padding.left() + width - pw, 0, pw, _pixh, _width).contains(x, y)) { inThumb = true; } @@ -2909,27 +2915,27 @@ HistoryTextState HistoryWebPage::getState(int x, int y, HistoryStateRequest requ } int symbolAdd = 0; if (_siteNameWidth) { - tshift += _lineHeight; + tshift += lineHeight; } if (_titleLines) { - if (y >= tshift && y < tshift + _titleLines * _lineHeight) { + if (y >= tshift && y < tshift + _titleLines * lineHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; result = _title.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, titleRequest); - } else if (y >= tshift + _titleLines * _lineHeight) { + } else if (y >= tshift + _titleLines * lineHeight) { symbolAdd += _title.length(); } - tshift += _titleLines * _lineHeight; + tshift += _titleLines * lineHeight; } if (_descriptionLines) { - if (y >= tshift && y < tshift + _descriptionLines * _lineHeight) { + if (y >= tshift && y < tshift + _descriptionLines * lineHeight) { Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; result = _description.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, descriptionRequest); - } else if (y >= tshift + _descriptionLines * _lineHeight) { + } else if (y >= tshift + _descriptionLines * lineHeight) { symbolAdd += _description.length(); } - tshift += _descriptionLines * _lineHeight; + tshift += _descriptionLines * lineHeight; } if (inThumb) { result.link = _openl; @@ -3054,9 +3060,11 @@ HistoryGame::HistoryGame(HistoryItem *parent, const HistoryGame &other) : Histor } void HistoryGame::initDimensions() { - if (!_lineHeight) _lineHeight = qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); + auto lineHeight = unitedLineHeight(); - if (!_openl) _openl.reset(new ReplyMarkupClickHandler(_parent, 0, 0)); + if (!_openl) { + _openl = MakeShared(_parent, 0, 0); + } auto title = _data->title; @@ -3081,6 +3089,9 @@ void HistoryGame::initDimensions() { if (_description.isEmpty() && !_data->description.isEmpty()) { auto text = _data->description; if (!text.isEmpty()) { + if (!_attach) { + text += _parent->skipBlock(); + } _description.setText(st::webPageDescriptionStyle, text, _webpageDescriptionOptions); } } @@ -3094,10 +3105,10 @@ void HistoryGame::initDimensions() { _maxw = skipBlockWidth; _minh = 0; - int32 titleMinHeight = _title.isEmpty() ? 0 : _lineHeight; + int32 titleMinHeight = _title.isEmpty() ? 0 : lineHeight; // enable any count of lines in game description / message int descMaxLines = 4096; - int32 descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * _lineHeight); + int32 descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); if (!_title.isEmpty()) { accumulate_max(_maxw, _title.maxWidth()); @@ -3134,7 +3145,8 @@ int HistoryGame::resizeGetHeight(int width) { width -= st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); // enable any count of lines in game description / message - int linesMax = 4096; + auto linesMax = 4096; + auto lineHeight = unitedLineHeight(); _height = 0; if (_title.isEmpty()) { _titleLines = 0; @@ -3144,7 +3156,7 @@ int HistoryGame::resizeGetHeight(int width) { } else { _titleLines = 2; } - _height += _titleLines * _lineHeight; + _height += _titleLines * lineHeight; } if (_description.isEmpty()) { @@ -3156,7 +3168,7 @@ int HistoryGame::resizeGetHeight(int width) { } else { _descriptionLines = (linesMax - _titleLines); } - _height += _descriptionLines * _lineHeight; + _height += _descriptionLines * lineHeight; } if (_attach) { @@ -3200,6 +3212,7 @@ void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, Time QRect bar(rtlrect(st::msgPadding.left(), tshift, st::webPageBar, _height - tshift - bshift, _width)); p.fillRect(bar, barfg); + auto lineHeight = unitedLineHeight(); if (_titleLines) { p.setPen(semibold); int32 endskip = 0; @@ -3207,7 +3220,7 @@ void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, Time endskip = _parent->skipBlockWidth(); } _title.drawLeftElided(p, padding.left(), tshift, width, _width, _titleLines, style::al_left, 0, -1, endskip, false, selection); - tshift += _titleLines * _lineHeight; + tshift += _titleLines * lineHeight; } if (_descriptionLines) { p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); @@ -3216,7 +3229,7 @@ void HistoryGame::draw(Painter &p, const QRect &r, TextSelection selection, Time endskip = _parent->skipBlockWidth(); } _description.drawLeftElided(p, padding.left(), tshift, width, _width, _descriptionLines, style::al_left, 0, -1, endskip, false, toDescriptionSelection(selection)); - tshift += _descriptionLines * _lineHeight; + tshift += _descriptionLines * lineHeight; } if (_attach) { auto attachAtTop = !_titleLines && !_descriptionLines; @@ -3263,27 +3276,28 @@ HistoryTextState HistoryGame::getState(int x, int y, HistoryStateRequest request } width -= padding.left() + padding.right(); - bool inThumb = false; - int symbolAdd = 0; + auto inThumb = false; + auto symbolAdd = 0; + auto lineHeight = unitedLineHeight(); if (_titleLines) { - if (y >= tshift && y < tshift + _titleLines * _lineHeight) { + if (y >= tshift && y < tshift + _titleLines * lineHeight) { Text::StateRequestElided titleRequest = request.forText(); titleRequest.lines = _titleLines; result = _title.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, titleRequest); - } else if (y >= tshift + _titleLines * _lineHeight) { + } else if (y >= tshift + _titleLines * lineHeight) { symbolAdd += _title.length(); } - tshift += _titleLines * _lineHeight; + tshift += _titleLines * lineHeight; } if (_descriptionLines) { - if (y >= tshift && y < tshift + _descriptionLines * _lineHeight) { + if (y >= tshift && y < tshift + _descriptionLines * lineHeight) { Text::StateRequestElided descriptionRequest = request.forText(); descriptionRequest.lines = _descriptionLines; result = _description.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, descriptionRequest); - } else if (y >= tshift + _descriptionLines * _lineHeight) { + } else if (y >= tshift + _descriptionLines * lineHeight) { symbolAdd += _description.length(); } - tshift += _descriptionLines * _lineHeight; + tshift += _descriptionLines * lineHeight; } if (inThumb) { result.link = _openl; @@ -3411,6 +3425,353 @@ int HistoryGame::bottomInfoPadding() const { return result; } +HistoryInvoice::HistoryInvoice(HistoryItem *parent, const MTPDmessageMediaInvoice &data) : HistoryMedia(parent) +, _title(st::msgMinWidth) +, _description(st::msgMinWidth) +, _status(st::msgMinWidth) { + fillFromData(data); +} + +HistoryInvoice::HistoryInvoice(HistoryItem *parent, const HistoryInvoice &other) : HistoryMedia(parent) +, _attach(other._attach ? other._attach->clone(parent) : nullptr) +, _titleHeight(other._titleHeight) +, _descriptionHeight(other._descriptionHeight) +, _title(other._title) +, _description(other._description) +, _status(other._status) { +} + +namespace { + +QString fillAmountAndCurrency(int amount, const QString ¤cy) { + static auto shortCurrencyNames = QMap { + { qsl("USD"), qsl("$") }, + { qsl("GBP"), qsl("£") }, + { qsl("EUR"), qsl("€") }, + { qsl("JPY"), qsl("¥") }, + }; + auto amountBucks = amount / 100; + auto amountCents = amount % 100; + auto amountText = qsl("%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0')); + auto currencyText = shortCurrencyNames.value(currency, currency); + return currencyText + amountText; +} + +} // namespace + +void HistoryInvoice::fillFromData(const MTPDmessageMediaInvoice &data) { + // init attach + if (data.has_photo()) { +// _attach = std::make_unique(_parent, data.vphoto.c_webDocument(), QString()); + } + + auto statusText = TextWithEntities { fillAmountAndCurrency(data.vtotal_amount.v, qs(data.vcurrency)), EntitiesInText() }; + statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size())); + if (data.is_test()) { + statusText.text += " TEST"; + } + _status.setMarkedText(st::defaultTextStyle, statusText, itemTextOptions(_parent)); + + // init strings + auto description = qs(data.vdescription); + if (!description.isEmpty()) { + _description.setText(st::webPageDescriptionStyle, description, _webpageDescriptionOptions); + } + auto title = qs(data.vtitle); + if (!title.isEmpty()) { + _title.setText(st::webPageTitleStyle, title, _webpageTitleOptions); + } +} + +void HistoryInvoice::initDimensions() { + auto lineHeight = unitedLineHeight(); + + if (_attach) { + if (_status.hasSkipBlock()) { + _status.removeSkipBlock(); + } + } else if (!_status.hasSkipBlock()) { + _status.setSkipBlock(_parent->skipBlockWidth(), _parent->skipBlockHeight()); + } + + // init dimensions + int32 l = st::msgPadding.left(), r = st::msgPadding.right(); + int32 skipBlockWidth = _parent->skipBlockWidth(); + _maxw = skipBlockWidth; + _minh = 0; + + int32 titleMinHeight = _title.isEmpty() ? 0 : lineHeight; + // enable any count of lines in game description / message + int descMaxLines = 4096; + int32 descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); + + if (!_title.isEmpty()) { + accumulate_max(_maxw, _title.maxWidth()); + _minh += titleMinHeight; + } + if (!_description.isEmpty()) { + accumulate_max(_maxw, _description.maxWidth()); + _minh += descriptionMinHeight; + } + if (_attach) { + auto attachAtTop = _title.isEmpty() && _description.isEmpty(); + if (!attachAtTop) _minh += st::mediaInBubbleSkip; + + _attach->initDimensions(); + auto bubble = _attach->bubbleMargins(); + auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); + if (isBubbleBottom() && _attach->customInfoLayout()) { + maxMediaWidth += skipBlockWidth; + } + accumulate_max(_maxw, maxMediaWidth); + _minh += _attach->minHeight() - bubble.top() - bubble.bottom(); + } else { + accumulate_max(_maxw, _status.maxWidth()); + _minh += st::mediaInBubbleSkip + _status.minHeight(); + } + auto padding = inBubblePadding(); + _maxw += padding.left() + padding.right(); + _minh += padding.top() + padding.bottom(); +} + +int HistoryInvoice::resizeGetHeight(int width) { + _width = width = qMin(width, _maxw); + width -= st::msgPadding.left() + st::msgPadding.right(); + + auto lineHeight = unitedLineHeight(); + + _height = 0; + if (_title.isEmpty()) { + _titleHeight = 0; + } else { + if (_title.countHeight(width) < 2 * st::webPageTitleFont->height) { + _titleHeight = lineHeight; + } else { + _titleHeight = 2 * lineHeight; + } + _height += _titleHeight; + } + + if (_description.isEmpty()) { + _descriptionHeight = 0; + } else { + _descriptionHeight = _description.countHeight(width); + _height += _descriptionHeight; + } + + if (_attach) { + auto attachAtTop = !_title.isEmpty() && _description.isEmpty(); + if (!attachAtTop) _height += st::mediaInBubbleSkip; + + QMargins bubble(_attach->bubbleMargins()); + + _attach->resizeGetHeight(width + bubble.left() + bubble.right()); + _height += _attach->height() - bubble.top() - bubble.bottom(); + if (isBubbleBottom() && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + _height += bottomInfoPadding(); + } + } else { + _height += st::mediaInBubbleSkip + _status.countHeight(width); + } + auto padding = inBubblePadding(); + _height += padding.top() + padding.bottom(); + + return _height; +} + +void HistoryInvoice::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const { + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return; + int32 width = _width, height = _height; + + bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost; + bool selected = (selection == FullSelection); + + auto &barfg = selected ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); + auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); + auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); + + QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + width -= padding.left() + padding.right(); + if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + + auto lineHeight = unitedLineHeight(); + if (_titleHeight) { + p.setPen(semibold); + p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outSemiboldPalette : st::inSemiboldPalette)); + + int32 endskip = 0; + if (_title.hasSkipBlock()) { + endskip = _parent->skipBlockWidth(); + } + _title.drawLeftElided(p, padding.left(), tshift, width, _width, _titleHeight / lineHeight, style::al_left, 0, -1, endskip, false, selection); + tshift += _titleHeight; + + p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette)); + } + if (_descriptionHeight) { + p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); + _description.drawLeft(p, padding.left(), tshift, width, _width, style::al_left, 0, -1, toDescriptionSelection(selection)); + tshift += _descriptionHeight; + } + if (_attach) { + auto attachAtTop = !_titleHeight && !_descriptionHeight; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; + + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); + + auto attachSelection = selected ? FullSelection : TextSelection { 0, 0 }; + + p.translate(attachLeft, attachTop); + _attach->draw(p, r.translated(-attachLeft, -attachTop), attachSelection, ms); + auto pixwidth = _attach->currentWidth(); + auto pixheight = _attach->height(); + + auto available = _status.maxWidth(); + auto statusW = available + 2 * st::msgDateImgPadding.x(); + auto statusH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y(); + auto statusX = st::msgDateImgDelta; + auto statusY = st::msgDateImgDelta; + + App::roundRect(p, rtlrect(statusX, statusY, statusW, statusH, pixwidth), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners); + + p.setFont(st::msgDateFont); + p.setPen(st::msgDateImgFg); + _status.drawLeftElided(p, statusX + st::msgDateImgPadding.x(), statusY + st::msgDateImgPadding.y(), available, pixwidth); + + p.translate(-attachLeft, -attachTop); + } else { + p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg); + _status.drawLeft(p, padding.left(), tshift + st::mediaInBubbleSkip, width, _width); + } +} + +HistoryTextState HistoryInvoice::getState(int x, int y, HistoryStateRequest request) const { + HistoryTextState result; + + if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result; + int32 width = _width, height = _height; + + QMargins bubble(_attach ? _attach->bubbleMargins() : QMargins()); + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + if (isBubbleBottom() && _attach && _attach->customInfoLayout() && _attach->currentWidth() + _parent->skipBlockWidth() > width + bubble.left() + bubble.right()) { + bshift += bottomInfoPadding(); + } + width -= padding.left() + padding.right(); + + auto lineHeight = unitedLineHeight(); + auto symbolAdd = 0; + if (_titleHeight) { + if (y >= tshift && y < tshift + _titleHeight) { + Text::StateRequestElided titleRequest = request.forText(); + titleRequest.lines = _titleHeight / lineHeight; + result = _title.getStateElidedLeft(x - padding.left(), y - tshift, width, _width, titleRequest); + } else if (y >= tshift + _titleHeight) { + symbolAdd += _title.length(); + } + tshift += _titleHeight; + } + if (_descriptionHeight) { + if (y >= tshift && y < tshift + _descriptionHeight) { + result = _description.getStateLeft(x - padding.left(), y - tshift, width, _width, request.forText()); + } else if (y >= tshift + _descriptionHeight) { + symbolAdd += _description.length(); + } + tshift += _descriptionHeight; + } + if (_attach) { + auto attachAtTop = !_titleHeight && !_descriptionHeight; + if (!attachAtTop) tshift += st::mediaInBubbleSkip; + + auto attachLeft = padding.left() - bubble.left(); + auto attachTop = tshift - bubble.top(); + if (rtl()) attachLeft = _width - attachLeft - _attach->currentWidth(); + + if (x >= attachLeft && x < attachLeft + _attach->currentWidth() && y >= tshift && y < _height - bshift) { + result = _attach->getState(x - attachLeft, y - attachTop, request); + } + } + + result.symbol += symbolAdd; + return result; +} + +TextSelection HistoryInvoice::adjustSelection(TextSelection selection, TextSelectType type) const { + if (!_descriptionHeight || selection.to <= _title.length()) { + return _title.adjustSelection(selection, type); + } + auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); + if (selection.from >= _title.length()) { + return fromDescriptionSelection(descriptionSelection); + } + auto titleSelection = _title.adjustSelection(selection, type); + return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to }; +} + +void HistoryInvoice::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { + if (_attach) { + _attach->clickHandlerActiveChanged(p, active); + } +} + +void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { + if (_attach) { + _attach->clickHandlerPressedChanged(p, pressed); + } +} + +void HistoryInvoice::attachToParent() { + if (_attach) _attach->attachToParent(); +} + +void HistoryInvoice::detachFromParent() { + if (_attach) _attach->detachFromParent(); +} + +QString HistoryInvoice::notificationText() const { + return _title.originalText(); +} + +TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const { + if (selection == FullSelection) { + return TextWithEntities(); + } + auto titleResult = _title.originalTextWithEntities(selection, ExpandLinksAll); + auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection), ExpandLinksAll); + if (titleResult.text.isEmpty()) { + return descriptionResult; + } else if (descriptionResult.text.isEmpty()) { + return titleResult; + } + + titleResult.text += '\n'; + appendTextWithEntities(titleResult, std::move(descriptionResult)); + return titleResult; +} + +QMargins HistoryInvoice::inBubblePadding() const { + auto lshift = st::msgPadding.left(); + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +int HistoryInvoice::bottomInfoPadding() const { + if (!isBubbleBottom()) return 0; + + auto result = st::msgDateFont->height; + return result; +} + HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent) , _data(App::location(coords)) , _title(st::msgMinWidth) diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h index 61b2b9e3c7..16f4269c0b 100644 --- a/Telegram/SourceFiles/history/history_media_types.h +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -46,16 +46,16 @@ protected: void setDocumentLinks(DocumentData *document, bool inlinegif = false) { ClickHandlerPtr open, save; if (inlinegif) { - open.reset(new GifOpenClickHandler(document)); + open = MakeShared(document); } else { - open.reset(new DocumentOpenClickHandler(document)); + open = MakeShared(document); } if (inlinegif) { - save.reset(new GifOpenClickHandler(document)); + save = MakeShared(document); } else if (document->voice()) { - save.reset(new DocumentOpenClickHandler(document)); + save = MakeShared(document); } else { - save.reset(new DocumentSaveClickHandler(document)); + save = MakeShared(document); } setLinks(std::move(open), std::move(save), MakeShared(document)); } @@ -118,8 +118,8 @@ public: HistoryMediaType type() const override { return MediaTypePhoto; } - HistoryPhoto *clone(HistoryItem *newParent) const override { - return new HistoryPhoto(newParent, *this); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, *this); } void initDimensions() override; @@ -205,8 +205,8 @@ public: HistoryMediaType type() const override { return MediaTypeVideo; } - HistoryVideo *clone(HistoryItem *newParent) const override { - return new HistoryVideo(newParent, *this); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, *this); } void initDimensions() override; @@ -357,8 +357,8 @@ public: HistoryMediaType type() const override { return _data->voice() ? MediaTypeVoiceFile : (_data->song() ? MediaTypeMusicFile : MediaTypeFile); } - HistoryDocument *clone(HistoryItem *newParent) const override { - return new HistoryDocument(newParent, *this); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, *this); } void initDimensions() override; @@ -456,8 +456,8 @@ public: HistoryMediaType type() const override { return MediaTypeGif; } - HistoryGif *clone(HistoryItem *newParent) const override { - return new HistoryGif(newParent, *this); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, *this); } void initDimensions() override; @@ -553,8 +553,8 @@ public: HistoryMediaType type() const override { return MediaTypeSticker; } - HistorySticker *clone(HistoryItem *newParent) const override { - return new HistorySticker(newParent, _data); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, _data); } void initDimensions() override; @@ -617,8 +617,8 @@ public: HistoryMediaType type() const override { return MediaTypeContact; } - HistoryContact *clone(HistoryItem *newParent) const override { - return new HistoryContact(newParent, _userId, _fname, _lname, _phone); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, _userId, _fname, _lname, _phone); } void initDimensions() override; @@ -680,8 +680,8 @@ public: HistoryMediaType type() const override { return MediaTypeWebPage; } - HistoryWebPage *clone(HistoryItem *newParent) const override { - return new HistoryWebPage(newParent, *this); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, *this); } void initDimensions() override; @@ -778,8 +778,8 @@ public: HistoryMediaType type() const override { return MediaTypeGame; } - HistoryGame *clone(HistoryItem *newParent) const override { - return new HistoryGame(newParent, *this); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, *this); } void initDimensions() override; @@ -871,6 +871,84 @@ private: }; +class HistoryInvoice : public HistoryMedia { +public: + HistoryInvoice(HistoryItem *parent, const MTPDmessageMediaInvoice &data); + HistoryInvoice(HistoryItem *parent, const HistoryInvoice &other); + HistoryMediaType type() const override { + return MediaTypeGame; + } + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, *this); + } + + void initDimensions() override; + int resizeGetHeight(int width) override; + + void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; + HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; + + TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; + bool hasTextForCopy() const override { + return false; // we do not add _title and _description in FullSelection text copy. + } + + bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { + return _attach && _attach->toggleSelectionByHandlerClick(p); + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return _attach && _attach->dragItemByHandler(p); + } + + QString notificationText() const override; + TextWithEntities selectedText(TextSelection selection) const override; + + void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + + void attachToParent() override; + void detachFromParent() override; + + bool hasReplyPreview() const override { + return _attach && _attach->hasReplyPreview(); + } + ImagePtr replyPreview() override { + return _attach ? _attach->replyPreview() : ImagePtr(); + } + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + + HistoryMedia *attach() const { + return _attach.get(); + } + +private: + void fillFromData(const MTPDmessageMediaInvoice &data); + + TextSelection toDescriptionSelection(TextSelection selection) const { + return internal::unshiftSelection(selection, _title); + } + TextSelection fromDescriptionSelection(TextSelection selection) const { + return internal::shiftSelection(selection, _title); + } + QMargins inBubblePadding() const; + int bottomInfoPadding() const; + + std::unique_ptr _attach; + + int _titleHeight; + int _descriptionHeight; + Text _title; + Text _description; + Text _status; + +}; + class LocationCoords; struct LocationData; @@ -881,8 +959,8 @@ public: HistoryMediaType type() const override { return MediaTypeLocation; } - HistoryLocation *clone(HistoryItem *newParent) const override { - return new HistoryLocation(newParent, *this); + std::unique_ptr clone(HistoryItem *newParent) const override { + return std::make_unique(newParent, *this); } void initDimensions() override; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index cc9cb89574..695683b11f 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -210,7 +210,7 @@ bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) { replyToLnk = goToMessageClickHandler(replyToMsg); if (!replyToMsg->Has()) { if (auto bot = replyToMsg->viaBot()) { - _replyToVia.reset(new HistoryMessageVia()); + _replyToVia = std::make_unique(); _replyToVia->create(peerToUser(bot->id)); } } @@ -474,7 +474,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags fl createComponents(config); if (mediaOriginal) { - _media.reset(mediaOriginal->clone(this)); + _media = mediaOriginal->clone(this); } setText(fwd->originalText()); } @@ -498,7 +498,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags : HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, markup); - _media.reset(new HistoryPhoto(this, photo, caption)); + _media = std::make_unique(this, photo, caption); setText(TextWithEntities()); } @@ -506,7 +506,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags : HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, markup); - _media.reset(new HistoryGame(this, game)); + _media = std::make_unique(this, game); setText(TextWithEntities()); } @@ -682,24 +682,24 @@ void HistoryMessage::initMedia(const MTPMessageMedia *media) { switch (media ? media->type() : mtpc_messageMediaEmpty) { case mtpc_messageMediaContact: { auto &d = media->c_messageMediaContact(); - _media.reset(new HistoryContact(this, d.vuser_id.v, qs(d.vfirst_name), qs(d.vlast_name), qs(d.vphone_number))); + _media = std::make_unique(this, d.vuser_id.v, qs(d.vfirst_name), qs(d.vlast_name), qs(d.vphone_number)); } break; case mtpc_messageMediaGeo: { auto &point = media->c_messageMediaGeo().vgeo; if (point.type() == mtpc_geoPoint) { - _media.reset(new HistoryLocation(this, LocationCoords(point.c_geoPoint()))); + _media = std::make_unique(this, LocationCoords(point.c_geoPoint())); } } break; case mtpc_messageMediaVenue: { auto &d = media->c_messageMediaVenue(); if (d.vgeo.type() == mtpc_geoPoint) { - _media.reset(new HistoryLocation(this, LocationCoords(d.vgeo.c_geoPoint()), qs(d.vtitle), qs(d.vaddress))); + _media = std::make_unique(this, LocationCoords(d.vgeo.c_geoPoint()), qs(d.vtitle), qs(d.vaddress)); } } break; case mtpc_messageMediaPhoto: { auto &photo = media->c_messageMediaPhoto(); if (photo.vphoto.type() == mtpc_photo) { - _media.reset(new HistoryPhoto(this, App::feedPhoto(photo.vphoto.c_photo()), qs(photo.vcaption))); + _media = std::make_unique(this, App::feedPhoto(photo.vphoto.c_photo()), qs(photo.vcaption)); } } break; case mtpc_messageMediaDocument: { @@ -713,10 +713,10 @@ void HistoryMessage::initMedia(const MTPMessageMedia *media) { switch (d.type()) { case mtpc_webPageEmpty: break; case mtpc_webPagePending: { - _media.reset(new HistoryWebPage(this, App::feedWebPage(d.c_webPagePending()))); + _media = std::make_unique(this, App::feedWebPage(d.c_webPagePending())); } break; case mtpc_webPage: { - _media.reset(new HistoryWebPage(this, App::feedWebPage(d.c_webPage()))); + _media = std::make_unique(this, App::feedWebPage(d.c_webPage())); } break; case mtpc_webPageNotModified: LOG(("API Error: webPageNotModified is unexpected in message media.")); break; } @@ -724,21 +724,24 @@ void HistoryMessage::initMedia(const MTPMessageMedia *media) { case mtpc_messageMediaGame: { auto &d = media->c_messageMediaGame().vgame; if (d.type() == mtpc_game) { - _media.reset(new HistoryGame(this, App::feedGame(d.c_game()))); + _media = std::make_unique(this, App::feedGame(d.c_game())); } } break; + case mtpc_messageMediaInvoice: { + _media = std::make_unique(this, media->c_messageMediaInvoice()); + } break; }; } void HistoryMessage::initMediaFromDocument(DocumentData *doc, const QString &caption) { if (doc->sticker()) { - _media.reset(new HistorySticker(this, doc)); + _media = std::make_unique(this, doc); } else if (doc->isAnimation()) { - _media.reset(new HistoryGif(this, doc, caption)); + _media = std::make_unique(this, doc, caption); } else if (doc->isVideo()) { - _media.reset(new HistoryVideo(this, doc, caption)); + _media = std::make_unique(this, doc, caption); } else { - _media.reset(new HistoryDocument(this, doc, caption)); + _media = std::make_unique(this, doc, caption); } } @@ -1026,7 +1029,7 @@ void HistoryMessage::setMedia(const MTPMessageMedia *media) { if (_media->type() == MediaTypeGame) return; mediaRemovedSkipBlock = _media->isDisplayed() && _media->isBubbleBottom(); - _media.clear(); + _media.reset(); } initMedia(media); auto mediaDisplayed = _media && _media->isDisplayed(); @@ -1834,7 +1837,7 @@ bool HistoryMessage::hasFromPhoto() const { } HistoryMessage::~HistoryMessage() { - _media.clear(); + _media.reset(); if (auto reply = Get()) { reply->clearData(this); } @@ -2036,7 +2039,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { case mtpc_messageActionChatEditPhoto: { auto &photo = action.c_messageActionChatEditPhoto().vphoto; if (photo.type() == mtpc_photo) { - _media.reset(new HistoryPhoto(this, history()->peer, photo.c_photo(), st::msgServicePhotoWidth)); + _media = std::make_unique(this, history()->peer, photo.c_photo(), st::msgServicePhotoWidth); } } break; @@ -2407,7 +2410,7 @@ void HistoryService::removeMedia() { if (!_media) return; bool mediaWasDisplayed = _media->isDisplayed(); - _media.clear(); + _media.reset(); if (mediaWasDisplayed) { _textWidth = -1; _textHeight = 0; @@ -2468,7 +2471,7 @@ void HistoryService::clearDependency() { HistoryService::~HistoryService() { clearDependency(); - _media.clear(); + _media.reset(); } HistoryJoined::HistoryJoined(History *history, const QDateTime &inviteDate, UserData *inviter, MTPDmessage::Flags flags)