/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/history_item_components.h" #include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" #include "ui/text_options.h" #include "history/history_message.h" #include "history/history_media.h" #include "history/history_media_types.h" #include "history/view/history_view_service_message.h" #include "media/media_audio.h" #include "media/player/media_player_instance.h" #include "styles/style_widgets.h" #include "styles/style_history.h" void HistoryMessageVia::create(UserId userId) { bot = App::user(peerFromUser(userId)); maxWidth = st::msgServiceNameFont->width( lng_inline_bot_via(lt_inline_bot, '@' + bot->username)); link = std::make_shared([bot = this->bot] { App::insertBotCommand('@' + bot->username); }); } void HistoryMessageVia::resize(int32 availw) const { if (availw < 0) { text = QString(); width = 0; } else { text = lng_inline_bot_via(lt_inline_bot, '@' + bot->username); if (availw < maxWidth) { text = st::msgServiceNameFont->elided(text, availw); width = st::msgServiceNameFont->width(text); } else if (width < maxWidth) { width = maxWidth; } } } void HistoryMessageSigned::refresh(const QString &date) { auto time = qsl(", ") + date; auto name = author; auto timew = st::msgDateFont->width(time); auto namew = st::msgDateFont->width(name); if (timew + namew > st::maxSignatureSize) { name = st::msgDateFont->elided(author, st::maxSignatureSize - timew); } signature.setText( st::msgDateTextStyle, name + time, Ui::NameTextOptions()); } int HistoryMessageSigned::maxWidth() const { return signature.maxWidth(); } void HistoryMessageEdited::refresh(const QString &date, bool displayed) { const auto prefix = displayed ? (lang(lng_edited) + ' ') : QString(); text.setText(st::msgDateTextStyle, prefix + date, Ui::NameTextOptions()); } int HistoryMessageEdited::maxWidth() const { return text.maxWidth(); } void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { auto phrase = QString(); auto fromChannel = (originalSender->isChannel() && !originalSender->isMegagroup()); if (!originalAuthor.isEmpty()) { phrase = lng_forwarded_signed( lt_channel, App::peerName(originalSender), lt_user, originalAuthor); } else { phrase = App::peerName(originalSender); } if (via) { if (fromChannel) { phrase = lng_forwarded_channel_via( lt_channel, textcmdLink(1, phrase), lt_inline_bot, textcmdLink(2, '@' + via->bot->username)); } else { phrase = lng_forwarded_via( lt_user, textcmdLink(1, phrase), lt_inline_bot, textcmdLink(2, '@' + via->bot->username)); } } else { if (fromChannel) { phrase = lng_forwarded_channel( lt_channel, textcmdLink(1, phrase)); } else { phrase = lng_forwarded( lt_user, textcmdLink(1, phrase)); } } TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto }; text.setText(st::fwdTextStyle, phrase, opts); text.setLink(1, fromChannel ? goToMessageClickHandler(originalSender, originalId) : originalSender->openLink()); if (via) { text.setLink(2, via->link); } } bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) { if (!force) { if (replyToMsg || !replyToMsgId) { return true; } } if (!replyToMsg) { replyToMsg = App::histItemById(holder->channelId(), replyToMsgId); if (replyToMsg) { if (replyToMsg->isEmpty()) { // Really it is deleted. replyToMsg = nullptr; force = true; } else { App::historyRegDependency(holder, replyToMsg); } } } if (replyToMsg) { replyToText.setText( st::messageTextStyle, TextUtilities::Clean(replyToMsg->inReplyText()), Ui::DialogTextOptions()); updateName(); replyToLnk = goToMessageClickHandler(replyToMsg); if (!replyToMsg->Has()) { if (auto bot = replyToMsg->viaBot()) { replyToVia = std::make_unique(); replyToVia->create(peerToUser(bot->id)); } } } else if (force) { replyToMsgId = 0; } if (force) { holder->setPendingInitDimensions(); } return (replyToMsg || !replyToMsgId); } void HistoryMessageReply::clearData(HistoryMessage *holder) { replyToVia = nullptr; if (replyToMsg) { App::historyUnregDependency(holder, replyToMsg); replyToMsg = nullptr; } replyToMsgId = 0; } bool HistoryMessageReply::isNameUpdated() const { if (replyToMsg && replyToMsg->author()->nameVersion > replyToVersion) { updateName(); return true; } return false; } void HistoryMessageReply::updateName() const { if (replyToMsg) { QString name = (replyToVia && replyToMsg->author()->isUser()) ? replyToMsg->author()->asUser()->firstName : App::peerName(replyToMsg->author()); replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions()); replyToVersion = replyToMsg->author()->nameVersion; bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; int32 w = replyToName.maxWidth(); if (replyToVia) { w += st::msgServiceFont->spacew + replyToVia->maxWidth; } maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize))); } else { maxReplyWidth = st::msgDateFont->width(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message)); } maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right(); } void HistoryMessageReply::resize(int width) const { if (replyToVia) { bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew); } } void HistoryMessageReply::itemRemoved(HistoryMessage *holder, HistoryItem *removed) { if (replyToMsg == removed) { clearData(holder); holder->setPendingInitDimensions(); } } void HistoryMessageReply::paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const { bool selected = (flags & PaintFlag::Selected), outbg = holder->hasOutLayout(); style::color bar = st::msgImgReplyBarColor; if (flags & PaintFlag::InBubble) { bar = (flags & PaintFlag::Selected) ? (outbg ? st::msgOutReplyBarSelColor : st::msgInReplyBarSelColor) : (outbg ? st::msgOutReplyBarColor : st::msgInReplyBarColor); } QRect rbar(rtlrect(x + st::msgReplyBarPos.x(), y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), w + 2 * x)); p.fillRect(rbar, bar); if (w > st::msgReplyBarSkip) { if (replyToMsg) { auto hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false; if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) { hasPreview = false; } auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; if (hasPreview) { ImagePtr replyPreview = replyToMsg->getMedia()->replyPreview(); if (!replyPreview->isNull()) { auto to = rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x); auto previewWidth = replyPreview->width() / cIntRetinaFactor(); auto previewHeight = replyPreview->height() / cIntRetinaFactor(); auto preview = replyPreview->pixSingle(previewWidth, previewHeight, to.width(), to.height(), ImageRoundRadius::Small, RectPart::AllCorners, selected ? &st::msgStickerOverlay : nullptr); p.drawPixmap(to.x(), to.y(), preview); } } if (w > st::msgReplyBarSkip + previewSkip) { if (flags & PaintFlag::InBubble) { p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg)); } else { p.setPen(st::msgImgReplyBarColor); } replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x); if (replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) { p.setFont(st::msgServiceFont); p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia->text); } auto replyToAsMsg = replyToMsg->toHistoryMessage(); if (!(flags & PaintFlag::InBubble)) { } else if ((replyToAsMsg && replyToAsMsg->emptyText()) || replyToMsg->serviceMsg()) { p.setPen(outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg)); } else { p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg)); } replyToText.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top() + st::msgServiceNameFont->height, w - st::msgReplyBarSkip - previewSkip, w + 2 * x); } } else { p.setFont(st::msgDateFont); auto &date = outbg ? (selected ? st::msgOutDateFgSelected : st::msgOutDateFg) : (selected ? st::msgInDateFgSelected : st::msgInDateFg); p.setPen((flags & PaintFlag::InBubble) ? date : st::msgDateImgFg); p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(lang(replyToMsgId ? lng_profile_loading : lng_deleted_message), w - st::msgReplyBarSkip)); } } } ReplyMarkupClickHandler::ReplyMarkupClickHandler( int row, int column, FullMsgId context) : _itemId(context) , _row(row) , _column(column) { } // Copy to clipboard support. void ReplyMarkupClickHandler::copyToClipboard() const { if (auto button = getButton()) { if (button->type == HistoryMessageMarkupButton::Type::Url) { auto url = QString::fromUtf8(button->data); if (!url.isEmpty()) { QApplication::clipboard()->setText(url); } } } } QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const { if (auto button = getButton()) { if (button->type == HistoryMessageMarkupButton::Type::Url) { return lang(lng_context_copy_link); } } return QString(); } // Finds the corresponding button in the items markup struct. // If the button is not found it returns nullptr. // Note: it is possible that we will point to the different button // than the one was used when constructing the handler, but not a big deal. const HistoryMessageMarkupButton *ReplyMarkupClickHandler::getButton() const { if (auto item = App::histItemById(_itemId)) { if (auto markup = item->Get()) { if (_row < markup->rows.size()) { auto &row = markup->rows[_row]; if (_column < row.size()) { return &row[_column]; } } } } return nullptr; } void ReplyMarkupClickHandler::onClickImpl() const { if (const auto item = App::histItemById(_itemId)) { App::activateBotCommand(item, _row, _column); } } // Returns the full text of the corresponding button. QString ReplyMarkupClickHandler::buttonText() const { if (const auto button = getButton()) { return button->text; } return QString(); } ReplyKeyboard::Button::Button() = default; ReplyKeyboard::Button::Button(Button &&other) = default; ReplyKeyboard::Button &ReplyKeyboard::Button::operator=( Button &&other) = default; ReplyKeyboard::Button::~Button() = default; ReplyKeyboard::ReplyKeyboard( not_null item, std::unique_ptr