mirror of https://github.com/procxx/kepka.git
				
				
				
			
		
			
				
	
	
		
			1810 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			1810 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			C++
		
	
	
	
/*
 | 
						|
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/view/history_view_message.h"
 | 
						|
 | 
						|
#include "history/view/history_view_cursor_state.h"
 | 
						|
#include "history/history_item_components.h"
 | 
						|
#include "history/history_message.h"
 | 
						|
#include "history/media/history_media.h"
 | 
						|
#include "history/media/history_media_web_page.h"
 | 
						|
#include "history/history.h"
 | 
						|
#include "data/data_session.h"
 | 
						|
#include "lang/lang_keys.h"
 | 
						|
#include "mainwidget.h"
 | 
						|
#include "mainwindow.h"
 | 
						|
#include "window/window_controller.h"
 | 
						|
#include "auth_session.h"
 | 
						|
#include "layout.h"
 | 
						|
#include "styles/style_widgets.h"
 | 
						|
#include "styles/style_history.h"
 | 
						|
#include "styles/style_dialogs.h"
 | 
						|
 | 
						|
namespace HistoryView {
 | 
						|
namespace {
 | 
						|
 | 
						|
class KeyboardStyle : public ReplyKeyboard::Style {
 | 
						|
public:
 | 
						|
	using ReplyKeyboard::Style::Style;
 | 
						|
 | 
						|
	int buttonRadius() const override;
 | 
						|
 | 
						|
	void startPaint(Painter &p) const override;
 | 
						|
	const style::TextStyle &textStyle() const override;
 | 
						|
	void repaint(not_null<const HistoryItem*> item) const override;
 | 
						|
 | 
						|
protected:
 | 
						|
	void paintButtonBg(
 | 
						|
		Painter &p,
 | 
						|
		const QRect &rect,
 | 
						|
		float64 howMuchOver) const override;
 | 
						|
	void paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageMarkupButton::Type type) const override;
 | 
						|
	void paintButtonLoading(Painter &p, const QRect &rect) const override;
 | 
						|
	int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
void KeyboardStyle::startPaint(Painter &p) const {
 | 
						|
	p.setPen(st::msgServiceFg);
 | 
						|
}
 | 
						|
 | 
						|
const style::TextStyle &KeyboardStyle::textStyle() const {
 | 
						|
	return st::serviceTextStyle;
 | 
						|
}
 | 
						|
 | 
						|
void KeyboardStyle::repaint(not_null<const HistoryItem*> item) const {
 | 
						|
	Auth().data().requestItemRepaint(item);
 | 
						|
}
 | 
						|
 | 
						|
int KeyboardStyle::buttonRadius() const {
 | 
						|
	return st::dateRadius;
 | 
						|
}
 | 
						|
 | 
						|
void KeyboardStyle::paintButtonBg(
 | 
						|
		Painter &p,
 | 
						|
		const QRect &rect,
 | 
						|
		float64 howMuchOver) const {
 | 
						|
	App::roundRect(p, rect, st::msgServiceBg, StickerCorners);
 | 
						|
	if (howMuchOver > 0) {
 | 
						|
		auto o = p.opacity();
 | 
						|
		p.setOpacity(o * howMuchOver);
 | 
						|
		App::roundRect(p, rect, st::msgBotKbOverBgAdd, BotKbOverCorners);
 | 
						|
		p.setOpacity(o);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void KeyboardStyle::paintButtonIcon(
 | 
						|
		Painter &p,
 | 
						|
		const QRect &rect,
 | 
						|
		int outerWidth,
 | 
						|
		HistoryMessageMarkupButton::Type type) const {
 | 
						|
	using Button = HistoryMessageMarkupButton;
 | 
						|
	auto getIcon = [](Button::Type type) -> const style::icon* {
 | 
						|
		switch (type) {
 | 
						|
		case Button::Type::Url: return &st::msgBotKbUrlIcon;
 | 
						|
		case Button::Type::SwitchInlineSame:
 | 
						|
		case Button::Type::SwitchInline: return &st::msgBotKbSwitchPmIcon;
 | 
						|
		}
 | 
						|
		return nullptr;
 | 
						|
	};
 | 
						|
	if (auto icon = getIcon(type)) {
 | 
						|
		icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, outerWidth);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void KeyboardStyle::paintButtonLoading(Painter &p, const QRect &rect) const {
 | 
						|
	auto icon = &st::historySendingInvertedIcon;
 | 
						|
	icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + rect.height() - icon->height() - st::msgBotKbIconPadding, rect.x() * 2 + rect.width());
 | 
						|
}
 | 
						|
 | 
						|
int KeyboardStyle::minButtonWidth(
 | 
						|
		HistoryMessageMarkupButton::Type type) const {
 | 
						|
	using Button = HistoryMessageMarkupButton;
 | 
						|
	int result = 2 * buttonPadding(), iconWidth = 0;
 | 
						|
	switch (type) {
 | 
						|
	case Button::Type::Url: iconWidth = st::msgBotKbUrlIcon.width(); break;
 | 
						|
	case Button::Type::SwitchInlineSame:
 | 
						|
	case Button::Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break;
 | 
						|
	case Button::Type::Callback:
 | 
						|
	case Button::Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break;
 | 
						|
	}
 | 
						|
	if (iconWidth > 0) {
 | 
						|
		result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding));
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
QString AdminBadgeText() {
 | 
						|
	return lang(lng_admin_badge);
 | 
						|
}
 | 
						|
 | 
						|
QString FastReplyText() {
 | 
						|
	return lang(lng_fast_reply);
 | 
						|
}
 | 
						|
 | 
						|
void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide) {
 | 
						|
	auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg);
 | 
						|
	auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow);
 | 
						|
	auto cors = selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners);
 | 
						|
	auto parts = RectPart::FullTop | RectPart::NoTopBottom | RectPart::Bottom;
 | 
						|
	if (tailSide == RectPart::Right) {
 | 
						|
		parts |= RectPart::BottomLeft;
 | 
						|
		p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
 | 
						|
		auto &tail = selected ? st::historyBubbleTailOutRightSelected : st::historyBubbleTailOutRight;
 | 
						|
		tail.paint(p, rect.x() + rect.width(), rect.y() + rect.height() - tail.height(), outerWidth);
 | 
						|
		p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh);
 | 
						|
	} else if (tailSide == RectPart::Left) {
 | 
						|
		parts |= RectPart::BottomRight;
 | 
						|
		p.fillRect(rect.x(), rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
 | 
						|
		auto &tail = selected ? (outbg ? st::historyBubbleTailOutLeftSelected : st::historyBubbleTailInLeftSelected) : (outbg ? st::historyBubbleTailOutLeft : st::historyBubbleTailInLeft);
 | 
						|
		tail.paint(p, rect.x() - tail.width(), rect.y() + rect.height() - tail.height(), outerWidth);
 | 
						|
		p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh);
 | 
						|
	} else {
 | 
						|
		parts |= RectPart::FullBottom;
 | 
						|
	}
 | 
						|
	App::roundRect(p, rect, bg, cors, &sh, parts);
 | 
						|
}
 | 
						|
 | 
						|
style::color FromNameFg(not_null<PeerData*> peer, bool selected) {
 | 
						|
	if (selected) {
 | 
						|
		const style::color colors[] = {
 | 
						|
			st::historyPeer1NameFgSelected,
 | 
						|
			st::historyPeer2NameFgSelected,
 | 
						|
			st::historyPeer3NameFgSelected,
 | 
						|
			st::historyPeer4NameFgSelected,
 | 
						|
			st::historyPeer5NameFgSelected,
 | 
						|
			st::historyPeer6NameFgSelected,
 | 
						|
			st::historyPeer7NameFgSelected,
 | 
						|
			st::historyPeer8NameFgSelected,
 | 
						|
		};
 | 
						|
		return colors[Data::PeerColorIndex(peer->id)];
 | 
						|
	} else {
 | 
						|
		const style::color colors[] = {
 | 
						|
			st::historyPeer1NameFg,
 | 
						|
			st::historyPeer2NameFg,
 | 
						|
			st::historyPeer3NameFg,
 | 
						|
			st::historyPeer4NameFg,
 | 
						|
			st::historyPeer5NameFg,
 | 
						|
			st::historyPeer6NameFg,
 | 
						|
			st::historyPeer7NameFg,
 | 
						|
			st::historyPeer8NameFg,
 | 
						|
		};
 | 
						|
		return colors[Data::PeerColorIndex(peer->id)];
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
LogEntryOriginal::LogEntryOriginal() = default;
 | 
						|
 | 
						|
LogEntryOriginal::LogEntryOriginal(LogEntryOriginal &&other)
 | 
						|
: page(std::move(other.page)) {
 | 
						|
}
 | 
						|
 | 
						|
LogEntryOriginal &LogEntryOriginal::operator=(LogEntryOriginal &&other) {
 | 
						|
	page = std::move(other.page);
 | 
						|
	return *this;
 | 
						|
}
 | 
						|
 | 
						|
LogEntryOriginal::~LogEntryOriginal() = default;
 | 
						|
 | 
						|
Message::Message(
 | 
						|
	not_null<ElementDelegate*> delegate,
 | 
						|
	not_null<HistoryMessage*> data)
 | 
						|
: Element(delegate, data) {
 | 
						|
	initLogEntryOriginal();
 | 
						|
}
 | 
						|
 | 
						|
not_null<HistoryMessage*> Message::message() const {
 | 
						|
	return static_cast<HistoryMessage*>(data().get());
 | 
						|
}
 | 
						|
 | 
						|
QSize Message::performCountOptimalSize() {
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
 | 
						|
	auto maxWidth = 0;
 | 
						|
	auto minHeight = 0;
 | 
						|
 | 
						|
	updateMediaInBubbleState();
 | 
						|
	refreshEditedBadge();
 | 
						|
 | 
						|
	auto mediaOnBottom = (logEntryOriginal() != nullptr)
 | 
						|
		|| (media && media->isDisplayed() && media->isBubbleBottom());
 | 
						|
	if (mediaOnBottom) {
 | 
						|
		// remove skip
 | 
						|
	} else {
 | 
						|
		// add skip
 | 
						|
	}
 | 
						|
 | 
						|
	if (drawBubble()) {
 | 
						|
		auto forwarded = item->Get<HistoryMessageForwarded>();
 | 
						|
		auto reply = item->Get<HistoryMessageReply>();
 | 
						|
		auto via = item->Get<HistoryMessageVia>();
 | 
						|
		auto entry = logEntryOriginal();
 | 
						|
		if (forwarded) {
 | 
						|
			forwarded->create(via);
 | 
						|
		}
 | 
						|
		if (reply) {
 | 
						|
			reply->updateName();
 | 
						|
		}
 | 
						|
		if (displayFromName()) {
 | 
						|
			item->updateAdminBadgeState();
 | 
						|
		}
 | 
						|
 | 
						|
		auto mediaDisplayed = false;
 | 
						|
		if (media) {
 | 
						|
			mediaDisplayed = media->isDisplayed();
 | 
						|
			media->initDimensions();
 | 
						|
		}
 | 
						|
		if (entry) {
 | 
						|
			entry->initDimensions();
 | 
						|
		}
 | 
						|
 | 
						|
		// Entry page is always a bubble bottom.
 | 
						|
		auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
 | 
						|
		auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
 | 
						|
 | 
						|
		if (mediaOnBottom) {
 | 
						|
			if (item->_text.removeSkipBlock()) {
 | 
						|
				item->_textWidth = -1;
 | 
						|
				item->_textHeight = 0;
 | 
						|
			}
 | 
						|
		} else if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) {
 | 
						|
			item->_textWidth = -1;
 | 
						|
			item->_textHeight = 0;
 | 
						|
		}
 | 
						|
 | 
						|
		maxWidth = plainMaxWidth();
 | 
						|
		minHeight = hasVisibleText() ? item->_text.minHeight() : 0;
 | 
						|
		if (!mediaOnBottom) {
 | 
						|
			minHeight += st::msgPadding.bottom();
 | 
						|
			if (mediaDisplayed) minHeight += st::mediaInBubbleSkip;
 | 
						|
		}
 | 
						|
		if (!mediaOnTop) {
 | 
						|
			minHeight += st::msgPadding.top();
 | 
						|
			if (mediaDisplayed) minHeight += st::mediaInBubbleSkip;
 | 
						|
			if (entry) minHeight += st::mediaInBubbleSkip;
 | 
						|
		}
 | 
						|
		if (mediaDisplayed) {
 | 
						|
			// Parts don't participate in maxWidth() in case of media message.
 | 
						|
			accumulate_max(maxWidth, media->maxWidth());
 | 
						|
			minHeight += media->minHeight();
 | 
						|
		} else {
 | 
						|
			// Count parts in maxWidth(), don't count them in minHeight().
 | 
						|
			// They will be added in resizeGetHeight() anyway.
 | 
						|
			if (displayFromName()) {
 | 
						|
				auto namew = st::msgPadding.left()
 | 
						|
					+ item->displayFrom()->nameText.maxWidth()
 | 
						|
					+ st::msgPadding.right();
 | 
						|
				if (via && !displayForwardedFrom()) {
 | 
						|
					namew += st::msgServiceFont->spacew + via->maxWidth;
 | 
						|
				}
 | 
						|
				const auto replyWidth = hasFastReply()
 | 
						|
					? st::msgFont->width(FastReplyText())
 | 
						|
					: 0;
 | 
						|
				if (item->hasAdminBadge()) {
 | 
						|
					const auto badgeWidth = st::msgFont->width(
 | 
						|
						AdminBadgeText());
 | 
						|
					namew += st::msgPadding.right()
 | 
						|
						+ std::max(badgeWidth, replyWidth);
 | 
						|
				} else if (replyWidth) {
 | 
						|
					namew += st::msgPadding.right() + replyWidth;
 | 
						|
				}
 | 
						|
				accumulate_max(maxWidth, namew);
 | 
						|
			} else if (via && !displayForwardedFrom()) {
 | 
						|
				accumulate_max(maxWidth, st::msgPadding.left() + via->maxWidth + st::msgPadding.right());
 | 
						|
			}
 | 
						|
			if (displayForwardedFrom()) {
 | 
						|
				auto namew = st::msgPadding.left() + forwarded->text.maxWidth() + st::msgPadding.right();
 | 
						|
				if (via) {
 | 
						|
					namew += st::msgServiceFont->spacew + via->maxWidth;
 | 
						|
				}
 | 
						|
				accumulate_max(maxWidth, 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(maxWidth, replyw);
 | 
						|
			}
 | 
						|
			if (entry) {
 | 
						|
				accumulate_max(maxWidth, entry->maxWidth());
 | 
						|
				minHeight += entry->minHeight();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (media) {
 | 
						|
		media->initDimensions();
 | 
						|
		maxWidth = media->maxWidth();
 | 
						|
		minHeight = media->isDisplayed() ? media->minHeight() : 0;
 | 
						|
	} else {
 | 
						|
		maxWidth = st::msgMinWidth;
 | 
						|
		minHeight = 0;
 | 
						|
	}
 | 
						|
	if (const auto markup = item->inlineReplyMarkup()) {
 | 
						|
		if (!markup->inlineKeyboard) {
 | 
						|
			markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(
 | 
						|
				item,
 | 
						|
				std::make_unique<KeyboardStyle>(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 (hasVisibleText()) {
 | 
						|
			accumulate_max(maxWidth, markup->inlineKeyboard->naturalWidth());
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return QSize(maxWidth, minHeight);
 | 
						|
}
 | 
						|
 | 
						|
int Message::marginTop() const {
 | 
						|
	auto result = 0;
 | 
						|
	if (!isHidden()) {
 | 
						|
		if (isAttachedToPrevious()) {
 | 
						|
			result += st::msgMarginTopAttached;
 | 
						|
		} else {
 | 
						|
			result += st::msgMargin.top();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	result += displayedDateHeight();
 | 
						|
	if (const auto bar = Get<UnreadBar>()) {
 | 
						|
		result += bar->height();
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
int Message::marginBottom() const {
 | 
						|
	return isHidden() ? 0 : st::msgMargin.bottom();
 | 
						|
}
 | 
						|
 | 
						|
void Message::draw(
 | 
						|
		Painter &p,
 | 
						|
		QRect clip,
 | 
						|
		TextSelection selection,
 | 
						|
		TimeMs ms) const {
 | 
						|
	auto g = countGeometry();
 | 
						|
	if (g.width() < 1) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
 | 
						|
	const auto outbg = hasOutLayout();
 | 
						|
	const auto bubble = drawBubble();
 | 
						|
	const auto selected = (selection == FullSelection);
 | 
						|
 | 
						|
	auto dateh = 0;
 | 
						|
	if (const auto date = Get<DateBadge>()) {
 | 
						|
		dateh = date->height();
 | 
						|
	}
 | 
						|
	if (const auto bar = Get<UnreadBar>()) {
 | 
						|
		auto unreadbarh = bar->height();
 | 
						|
		if (clip.intersects(QRect(0, dateh, width(), unreadbarh))) {
 | 
						|
			p.translate(0, dateh);
 | 
						|
			bar->paint(p, 0, width());
 | 
						|
			p.translate(0, -dateh);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (isHidden()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	paintHighlight(p, g.height());
 | 
						|
 | 
						|
	p.setTextPalette(selected
 | 
						|
		? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected)
 | 
						|
		: (outbg ? st::outTextPalette : st::inTextPalette));
 | 
						|
 | 
						|
	auto keyboard = item->inlineReplyKeyboard();
 | 
						|
	if (keyboard) {
 | 
						|
		auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
 | 
						|
		g.setHeight(g.height() - keyboardHeight);
 | 
						|
		auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin);
 | 
						|
		p.translate(keyboardPosition);
 | 
						|
		keyboard->paint(p, g.width(), clip.translated(-keyboardPosition), ms);
 | 
						|
		p.translate(-keyboardPosition);
 | 
						|
	}
 | 
						|
 | 
						|
	if (bubble) {
 | 
						|
		if (displayFromName() && item->displayFrom()->nameVersion > item->_fromNameVersion) {
 | 
						|
			fromNameUpdated(g.width());
 | 
						|
		}
 | 
						|
 | 
						|
		auto entry = logEntryOriginal();
 | 
						|
		auto mediaDisplayed = media && media->isDisplayed();
 | 
						|
 | 
						|
		auto skipTail = isAttachedToNext()
 | 
						|
			|| (media && media->skipBubbleTail())
 | 
						|
			|| (keyboard != nullptr);
 | 
						|
		auto displayTail = skipTail ? RectPart::None : (outbg && !Adaptive::ChatWide()) ? RectPart::Right : RectPart::Left;
 | 
						|
		PaintBubble(p, g, width(), selected, outbg, displayTail);
 | 
						|
 | 
						|
		// Entry page is always a bubble bottom.
 | 
						|
		auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
 | 
						|
		auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->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);
 | 
						|
			paintForwardedInfo(p, trect, selected);
 | 
						|
			paintReplyInfo(p, trect, selected);
 | 
						|
			paintViaBotIdInfo(p, trect, selected);
 | 
						|
		}
 | 
						|
		if (entry) {
 | 
						|
			trect.setHeight(trect.height() - entry->height());
 | 
						|
		}
 | 
						|
		paintText(p, trect, selection);
 | 
						|
		if (mediaDisplayed) {
 | 
						|
			auto mediaHeight = media->height();
 | 
						|
			auto mediaLeft = g.left();
 | 
						|
			auto mediaTop = (trect.y() + trect.height() - mediaHeight);
 | 
						|
 | 
						|
			p.translate(mediaLeft, mediaTop);
 | 
						|
			media->draw(p, clip.translated(-mediaLeft, -mediaTop), skipTextSelection(selection), ms);
 | 
						|
			p.translate(-mediaLeft, -mediaTop);
 | 
						|
		}
 | 
						|
		if (entry) {
 | 
						|
			auto entryLeft = g.left();
 | 
						|
			auto entryTop = trect.y() + trect.height();
 | 
						|
			p.translate(entryLeft, entryTop);
 | 
						|
			auto entrySelection = skipTextSelection(selection);
 | 
						|
			if (mediaDisplayed) {
 | 
						|
				entrySelection = media->skipSelection(entrySelection);
 | 
						|
			}
 | 
						|
			entry->draw(p, clip.translated(-entryLeft, -entryTop), entrySelection, ms);
 | 
						|
			p.translate(-entryLeft, -entryTop);
 | 
						|
		}
 | 
						|
		const auto needDrawInfo = entry
 | 
						|
			? !entry->customInfoLayout()
 | 
						|
			: (mediaDisplayed
 | 
						|
				? !media->customInfoLayout()
 | 
						|
				: true);
 | 
						|
		if (needDrawInfo) {
 | 
						|
			drawInfo(p, g.left() + g.width(), g.top() + g.height(), 2 * g.left() + g.width(), selected, InfoDisplayType::Default);
 | 
						|
		}
 | 
						|
		if (displayRightAction()) {
 | 
						|
			const auto fastShareSkip = snap(
 | 
						|
				(g.height() - st::historyFastShareSize) / 2,
 | 
						|
				0,
 | 
						|
				st::historyFastShareBottom);
 | 
						|
			const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft;
 | 
						|
			const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize;
 | 
						|
			drawRightAction(p, fastShareLeft, fastShareTop, width());
 | 
						|
		}
 | 
						|
	} else if (media && media->isDisplayed()) {
 | 
						|
		p.translate(g.topLeft());
 | 
						|
		media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms);
 | 
						|
		p.translate(-g.topLeft());
 | 
						|
	}
 | 
						|
 | 
						|
	p.restoreTextPalette();
 | 
						|
 | 
						|
	const auto reply = item->Get<HistoryMessageReply>();
 | 
						|
	if (reply && reply->isNameUpdated()) {
 | 
						|
		const_cast<Message*>(this)->setPendingResize();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Message::paintFromName(
 | 
						|
		Painter &p,
 | 
						|
		QRect &trect,
 | 
						|
		bool selected) const {
 | 
						|
	const auto item = message();
 | 
						|
	if (displayFromName()) {
 | 
						|
		const auto badgeWidth = [&] {
 | 
						|
			if (item->hasAdminBadge()) {
 | 
						|
				return st::msgFont->width(AdminBadgeText());
 | 
						|
			}
 | 
						|
			return 0;
 | 
						|
		}();
 | 
						|
		const auto replyWidth = [&] {
 | 
						|
			if (isUnderCursor() && displayFastReply()) {
 | 
						|
				return st::msgFont->width(FastReplyText());
 | 
						|
			}
 | 
						|
			return 0;
 | 
						|
		}();
 | 
						|
		const auto rightWidth = replyWidth ? replyWidth : badgeWidth;
 | 
						|
		auto availableLeft = trect.left();
 | 
						|
		auto availableWidth = trect.width();
 | 
						|
		if (rightWidth) {
 | 
						|
			availableWidth -= st::msgPadding.right() + rightWidth;
 | 
						|
		}
 | 
						|
 | 
						|
		p.setFont(st::msgNameFont);
 | 
						|
		if (item->isPost()) {
 | 
						|
			p.setPen(selected ? st::msgInServiceFgSelected : st::msgInServiceFg);
 | 
						|
		} else {
 | 
						|
			p.setPen(FromNameFg(item->displayFrom(), selected));
 | 
						|
		}
 | 
						|
		item->displayFrom()->nameText.drawElided(p, availableLeft, trect.top(), availableWidth);
 | 
						|
		auto skipWidth = item->displayFrom()->nameText.maxWidth() + st::msgServiceFont->spacew;
 | 
						|
		availableLeft += skipWidth;
 | 
						|
		availableWidth -= skipWidth;
 | 
						|
 | 
						|
		auto via = item->Get<HistoryMessageVia>();
 | 
						|
		if (via && !displayForwardedFrom() && availableWidth > 0) {
 | 
						|
			const auto outbg = hasOutLayout();
 | 
						|
			p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
 | 
						|
			p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->text);
 | 
						|
			auto skipWidth = via->width + st::msgServiceFont->spacew;
 | 
						|
			availableLeft += skipWidth;
 | 
						|
			availableWidth -= skipWidth;
 | 
						|
		}
 | 
						|
		if (rightWidth) {
 | 
						|
			p.setPen(selected ? st::msgInDateFgSelected : st::msgInDateFg);
 | 
						|
			p.setFont(ClickHandler::showAsActive(_fastReplyLink)
 | 
						|
				? st::msgFont->underline()
 | 
						|
				: st::msgFont);
 | 
						|
			p.drawText(
 | 
						|
				trect.left() + trect.width() - rightWidth,
 | 
						|
				trect.top() + st::msgFont->ascent,
 | 
						|
				replyWidth ? FastReplyText() : AdminBadgeText());
 | 
						|
		}
 | 
						|
		trect.setY(trect.y() + st::msgNameFont->height);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Message::paintForwardedInfo(Painter &p, QRect &trect, bool selected) const {
 | 
						|
	if (displayForwardedFrom()) {
 | 
						|
		style::font serviceFont(st::msgServiceFont), serviceName(st::msgServiceNameFont);
 | 
						|
 | 
						|
		const auto item = message();
 | 
						|
		const auto outbg = hasOutLayout();
 | 
						|
		p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
 | 
						|
		p.setFont(serviceFont);
 | 
						|
 | 
						|
		auto forwarded = item->Get<HistoryMessageForwarded>();
 | 
						|
		auto breakEverywhere = (forwarded->text.countHeight(trect.width()) > 2 * serviceFont->height);
 | 
						|
		p.setTextPalette(selected ? (outbg ? st::outFwdTextPaletteSelected : st::inFwdTextPaletteSelected) : (outbg ? st::outFwdTextPalette : st::inFwdTextPalette));
 | 
						|
		forwarded->text.drawElided(p, trect.x(), trect.y(), trect.width(), 2, style::al_left, 0, -1, 0, breakEverywhere);
 | 
						|
		p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette));
 | 
						|
 | 
						|
		trect.setY(trect.y() + (((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * serviceFont->height));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Message::paintReplyInfo(Painter &p, QRect &trect, bool selected) const {
 | 
						|
	const auto item = message();
 | 
						|
	if (auto reply = item->Get<HistoryMessageReply>()) {
 | 
						|
		int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
 | 
						|
 | 
						|
		auto flags = HistoryMessageReply::PaintFlag::InBubble | 0;
 | 
						|
		if (selected) {
 | 
						|
			flags |= HistoryMessageReply::PaintFlag::Selected;
 | 
						|
		}
 | 
						|
		reply->paint(p, this, trect.x(), trect.y(), trect.width(), flags);
 | 
						|
 | 
						|
		trect.setY(trect.y() + h);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Message::paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const {
 | 
						|
	const auto item = message();
 | 
						|
	if (!displayFromName() && !displayForwardedFrom()) {
 | 
						|
		if (auto via = item->Get<HistoryMessageVia>()) {
 | 
						|
			const auto outbg = hasOutLayout();
 | 
						|
			p.setFont(st::msgServiceNameFont);
 | 
						|
			p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
 | 
						|
			p.drawTextLeft(trect.left(), trect.top(), width(), via->text);
 | 
						|
			trect.setY(trect.y() + st::msgServiceNameFont->height);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Message::paintText(Painter &p, QRect &trect, TextSelection selection) const {
 | 
						|
	if (!hasVisibleText()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto item = message();
 | 
						|
 | 
						|
	const auto outbg = hasOutLayout();
 | 
						|
	auto selected = (selection == FullSelection);
 | 
						|
	p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
 | 
						|
	p.setFont(st::msgFont);
 | 
						|
	item->_text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, selection);
 | 
						|
}
 | 
						|
 | 
						|
PointState Message::pointState(QPoint point) const {
 | 
						|
	const auto g = countGeometry();
 | 
						|
	if (g.width() < 1 || isHidden()) {
 | 
						|
		return PointState::Outside;
 | 
						|
	}
 | 
						|
 | 
						|
	const auto media = this->media();
 | 
						|
	const auto item = message();
 | 
						|
	if (drawBubble()) {
 | 
						|
		if (!g.contains(point)) {
 | 
						|
			return PointState::Outside;
 | 
						|
		}
 | 
						|
		if (const auto mediaDisplayed = media && media->isDisplayed()) {
 | 
						|
			// Hack for grouped media point state.
 | 
						|
			auto entry = logEntryOriginal();
 | 
						|
 | 
						|
			// Entry page is always a bubble bottom.
 | 
						|
			auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
 | 
						|
			auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->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 {
 | 
						|
			//	if (getStateFromName(point, trect, &result)) return result;
 | 
						|
			//	if (getStateForwardedInfo(point, trect, &result, request)) return result;
 | 
						|
			//	if (getStateReplyInfo(point, trect, &result)) return result;
 | 
						|
			//	if (getStateViaBotIdInfo(point, trect, &result)) return result;
 | 
						|
			//}
 | 
						|
			if (entry) {
 | 
						|
				auto entryHeight = entry->height();
 | 
						|
				trect.setHeight(trect.height() - entryHeight);
 | 
						|
			}
 | 
						|
 | 
						|
			auto mediaHeight = media->height();
 | 
						|
			auto mediaLeft = trect.x() - st::msgPadding.left();
 | 
						|
			auto mediaTop = (trect.y() + trect.height() - mediaHeight);
 | 
						|
 | 
						|
			if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) {
 | 
						|
				return media->pointState(point - QPoint(mediaLeft, mediaTop));
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return PointState::Inside;
 | 
						|
	} else if (media) {
 | 
						|
		return media->pointState(point - g.topLeft());
 | 
						|
	}
 | 
						|
	return PointState::Outside;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::displayFromPhoto() const {
 | 
						|
	return hasFromPhoto() && !isAttachedToNext();
 | 
						|
}
 | 
						|
 | 
						|
bool Message::hasFromPhoto() const {
 | 
						|
	if (isHidden()) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	switch (context()) {
 | 
						|
	case Context::AdminLog:
 | 
						|
	case Context::Feed:
 | 
						|
		return true;
 | 
						|
	case Context::History: {
 | 
						|
		const auto item = message();
 | 
						|
		if (item->isPost() || item->isEmpty()) {
 | 
						|
			return false;
 | 
						|
		} else if (Adaptive::ChatWide()) {
 | 
						|
			return true;
 | 
						|
		} else if (item->history()->peer->isSelf()) {
 | 
						|
			return item->Has<HistoryMessageForwarded>();
 | 
						|
		}
 | 
						|
		return !item->out() && !item->history()->peer->isUser();
 | 
						|
	} break;
 | 
						|
	case Context::ContactPreview:
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	Unexpected("Context in Message::hasFromPhoto.");
 | 
						|
}
 | 
						|
 | 
						|
TextState Message::textState(
 | 
						|
		QPoint point,
 | 
						|
		StateRequest request) const {
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
 | 
						|
	auto result = TextState(item);
 | 
						|
 | 
						|
	auto g = countGeometry();
 | 
						|
	if (g.width() < 1 || isHidden()) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	auto keyboard = item->inlineReplyKeyboard();
 | 
						|
	auto keyboardHeight = 0;
 | 
						|
	if (keyboard) {
 | 
						|
		keyboardHeight = keyboard->naturalHeight();
 | 
						|
		g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
 | 
						|
	}
 | 
						|
 | 
						|
	if (drawBubble()) {
 | 
						|
		const auto inBubble = g.contains(point);
 | 
						|
		auto entry = logEntryOriginal();
 | 
						|
		auto mediaDisplayed = media && media->isDisplayed();
 | 
						|
 | 
						|
		// Entry page is always a bubble bottom.
 | 
						|
		auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
 | 
						|
		auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->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 if (inBubble) {
 | 
						|
			if (getStateFromName(point, trect, &result)) {
 | 
						|
				return result;
 | 
						|
			}
 | 
						|
			if (getStateForwardedInfo(point, trect, &result, request)) {
 | 
						|
				return result;
 | 
						|
			}
 | 
						|
			if (getStateReplyInfo(point, trect, &result)) {
 | 
						|
				return result;
 | 
						|
			}
 | 
						|
			if (getStateViaBotIdInfo(point, trect, &result)) {
 | 
						|
				return result;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (entry) {
 | 
						|
			auto entryHeight = entry->height();
 | 
						|
			trect.setHeight(trect.height() - entryHeight);
 | 
						|
			auto entryLeft = g.left();
 | 
						|
			auto entryTop = trect.y() + trect.height();
 | 
						|
			if (point.y() >= entryTop && point.y() < entryTop + entryHeight) {
 | 
						|
				result = entry->textState(
 | 
						|
					point - QPoint(entryLeft, entryTop),
 | 
						|
					request);
 | 
						|
				result.symbol += item->_text.length() + (mediaDisplayed ? media->fullSelectionLength() : 0);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		auto checkForPointInTime = [&] {
 | 
						|
			if (mediaOnBottom && (entry || media->customInfoLayout())) {
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			const auto inDate = pointInTime(
 | 
						|
				g.left() + g.width(),
 | 
						|
				g.top() + g.height(),
 | 
						|
				point,
 | 
						|
				InfoDisplayType::Default);
 | 
						|
			if (inDate) {
 | 
						|
				result.cursor = CursorState::Date;
 | 
						|
			}
 | 
						|
		};
 | 
						|
		if (inBubble) {
 | 
						|
			if (mediaDisplayed) {
 | 
						|
				auto mediaHeight = media->height();
 | 
						|
				auto mediaLeft = trect.x() - st::msgPadding.left();
 | 
						|
				auto mediaTop = (trect.y() + trect.height() - mediaHeight);
 | 
						|
 | 
						|
				if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) {
 | 
						|
					result = media->textState(point - QPoint(mediaLeft, mediaTop), request);
 | 
						|
					result.symbol += item->_text.length();
 | 
						|
				} else if (getStateText(point, trect, &result, request)) {
 | 
						|
					checkForPointInTime();
 | 
						|
					return result;
 | 
						|
				} else if (point.y() >= trect.y() + trect.height()) {
 | 
						|
					result.symbol = item->_text.length();
 | 
						|
				}
 | 
						|
			} else if (getStateText(point, trect, &result, request)) {
 | 
						|
				checkForPointInTime();
 | 
						|
				return result;
 | 
						|
			} else if (point.y() >= trect.y() + trect.height()) {
 | 
						|
				result.symbol = item->_text.length();
 | 
						|
			}
 | 
						|
		}
 | 
						|
		checkForPointInTime();
 | 
						|
		if (displayRightAction()) {
 | 
						|
			const auto fastShareSkip = snap(
 | 
						|
				(g.height() - st::historyFastShareSize) / 2,
 | 
						|
				0,
 | 
						|
				st::historyFastShareBottom);
 | 
						|
			const auto fastShareLeft = g.left() + g.width() + st::historyFastShareLeft;
 | 
						|
			const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize;
 | 
						|
			if (QRect(
 | 
						|
				fastShareLeft,
 | 
						|
				fastShareTop,
 | 
						|
				st::historyFastShareSize,
 | 
						|
				st::historyFastShareSize
 | 
						|
			).contains(point)) {
 | 
						|
				result.link = rightActionLink();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (media && media->isDisplayed()) {
 | 
						|
		result = media->textState(point - g.topLeft(), request);
 | 
						|
		result.symbol += item->_text.length();
 | 
						|
	}
 | 
						|
 | 
						|
	if (keyboard && !item->isLogEntry()) {
 | 
						|
		auto keyboardTop = g.top() + g.height() + st::msgBotKbButton.margin;
 | 
						|
		if (QRect(g.left(), keyboardTop, g.width(), keyboardHeight).contains(point)) {
 | 
						|
			result.link = keyboard->getLink(point - QPoint(g.left(), keyboardTop));
 | 
						|
			return result;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::getStateFromName(
 | 
						|
		QPoint point,
 | 
						|
		QRect &trect,
 | 
						|
		not_null<TextState*> outResult) const {
 | 
						|
	const auto item = message();
 | 
						|
	if (displayFromName()) {
 | 
						|
		const auto replyWidth = [&] {
 | 
						|
			if (isUnderCursor() && displayFastReply()) {
 | 
						|
				return st::msgFont->width(FastReplyText());
 | 
						|
			}
 | 
						|
			return 0;
 | 
						|
		}();
 | 
						|
		if (replyWidth
 | 
						|
			&& point.x() >= trect.left() + trect.width() - replyWidth
 | 
						|
			&& point.x() < trect.left() + trect.width() + st::msgPadding.right()
 | 
						|
			&& point.y() >= trect.top() - st::msgPadding.top()
 | 
						|
			&& point.y() < trect.top() + st::msgServiceFont->height) {
 | 
						|
			outResult->link = fastReplyLink();
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
 | 
						|
			auto availableLeft = trect.left();
 | 
						|
			auto availableWidth = trect.width();
 | 
						|
			if (replyWidth) {
 | 
						|
				availableWidth -= st::msgPadding.right() + replyWidth;
 | 
						|
			}
 | 
						|
			auto user = item->displayFrom();
 | 
						|
			if (point.x() >= availableLeft
 | 
						|
				&& point.x() < availableLeft + availableWidth
 | 
						|
				&& point.x() < availableLeft + user->nameText.maxWidth()) {
 | 
						|
				outResult->link = user->openLink();
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
			auto via = item->Get<HistoryMessageVia>();
 | 
						|
			if (via
 | 
						|
				&& !displayForwardedFrom()
 | 
						|
				&& point.x() >= availableLeft + item->displayFrom()->nameText.maxWidth() + st::msgServiceFont->spacew
 | 
						|
				&& point.x() < availableLeft + availableWidth
 | 
						|
				&& point.x() < availableLeft + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->width) {
 | 
						|
				outResult->link = via->link;
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		trect.setTop(trect.top() + st::msgNameFont->height);
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::getStateForwardedInfo(
 | 
						|
		QPoint point,
 | 
						|
		QRect &trect,
 | 
						|
		not_null<TextState*> outResult,
 | 
						|
		StateRequest request) const {
 | 
						|
	if (displayForwardedFrom()) {
 | 
						|
		const auto item = message();
 | 
						|
		auto forwarded = item->Get<HistoryMessageForwarded>();
 | 
						|
		auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
 | 
						|
		if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) {
 | 
						|
			auto breakEverywhere = (forwarded->text.countHeight(trect.width()) > 2 * st::semiboldFont->height);
 | 
						|
			auto textRequest = request.forText();
 | 
						|
			if (breakEverywhere) {
 | 
						|
				textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
 | 
						|
			}
 | 
						|
			*outResult = TextState(item, forwarded->text.getState(
 | 
						|
				point - trect.topLeft(),
 | 
						|
				trect.width(),
 | 
						|
				textRequest));
 | 
						|
			outResult->symbol = 0;
 | 
						|
			outResult->afterSymbol = false;
 | 
						|
			if (breakEverywhere) {
 | 
						|
				outResult->cursor = CursorState::Forwarded;
 | 
						|
			} else {
 | 
						|
				outResult->cursor = CursorState::None;
 | 
						|
			}
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		trect.setTop(trect.top() + fwdheight);
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::getStateReplyInfo(
 | 
						|
		QPoint point,
 | 
						|
		QRect &trect,
 | 
						|
		not_null<TextState*> outResult) const {
 | 
						|
	const auto item = message();
 | 
						|
	if (auto reply = item->Get<HistoryMessageReply>()) {
 | 
						|
		int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
 | 
						|
		if (point.y() >= trect.top() && point.y() < trect.top() + h) {
 | 
						|
			if (reply->replyToMsg && QRect(trect.x(), trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()).contains(point)) {
 | 
						|
				outResult->link = reply->replyToLink();
 | 
						|
			}
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		trect.setTop(trect.top() + h);
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::getStateViaBotIdInfo(
 | 
						|
		QPoint point,
 | 
						|
		QRect &trect,
 | 
						|
		not_null<TextState*> outResult) const {
 | 
						|
	const auto item = message();
 | 
						|
	if (const auto via = item->Get<HistoryMessageVia>()) {
 | 
						|
		if (!displayFromName() && !displayForwardedFrom()) {
 | 
						|
			if (QRect(trect.x(), trect.y(), via->width, st::msgNameFont->height).contains(point)) {
 | 
						|
				outResult->link = via->link;
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
			trect.setTop(trect.top() + st::msgNameFont->height);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::getStateText(
 | 
						|
		QPoint point,
 | 
						|
		QRect &trect,
 | 
						|
		not_null<TextState*> outResult,
 | 
						|
		StateRequest request) const {
 | 
						|
	if (!hasVisibleText()) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	const auto item = message();
 | 
						|
	if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) {
 | 
						|
		*outResult = TextState(item, item->_text.getState(
 | 
						|
			point - trect.topLeft(),
 | 
						|
			trect.width(),
 | 
						|
			request.forText()));
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
// Forward to media.
 | 
						|
void Message::updatePressed(QPoint point) {
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
	if (!media) return;
 | 
						|
 | 
						|
	auto g = countGeometry();
 | 
						|
	auto keyboard = item->inlineReplyKeyboard();
 | 
						|
	if (keyboard) {
 | 
						|
		auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
 | 
						|
		g.setHeight(g.height() - keyboardHeight);
 | 
						|
	}
 | 
						|
 | 
						|
	if (drawBubble()) {
 | 
						|
		auto mediaDisplayed = media && media->isDisplayed();
 | 
						|
		auto top = marginTop();
 | 
						|
		auto trect = g.marginsAdded(-st::msgPadding);
 | 
						|
		if (mediaDisplayed && media->isBubbleTop()) {
 | 
						|
			trect.setY(trect.y() - st::msgPadding.top());
 | 
						|
		} else {
 | 
						|
			if (displayFromName()) {
 | 
						|
				trect.setTop(trect.top() + st::msgNameFont->height);
 | 
						|
			}
 | 
						|
			if (displayForwardedFrom()) {
 | 
						|
				auto forwarded = item->Get<HistoryMessageForwarded>();
 | 
						|
				auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
 | 
						|
				trect.setTop(trect.top() + fwdheight);
 | 
						|
			}
 | 
						|
			if (item->Get<HistoryMessageReply>()) {
 | 
						|
				auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
 | 
						|
				trect.setTop(trect.top() + h);
 | 
						|
			}
 | 
						|
			if (const auto via = item->Get<HistoryMessageVia>()) {
 | 
						|
				if (!displayFromName() && !displayForwardedFrom()) {
 | 
						|
					trect.setTop(trect.top() + st::msgNameFont->height);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (mediaDisplayed && media->isBubbleBottom()) {
 | 
						|
			trect.setHeight(trect.height() + st::msgPadding.bottom());
 | 
						|
		}
 | 
						|
 | 
						|
		auto needDateCheck = true;
 | 
						|
		if (mediaDisplayed) {
 | 
						|
			auto mediaHeight = media->height();
 | 
						|
			auto mediaLeft = trect.x() - st::msgPadding.left();
 | 
						|
			auto mediaTop = (trect.y() + trect.height() - mediaHeight);
 | 
						|
			media->updatePressed(point - QPoint(mediaLeft, mediaTop));
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		media->updatePressed(point - g.topLeft());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
TextWithEntities Message::selectedText(TextSelection selection) const {
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
 | 
						|
	TextWithEntities logEntryOriginalResult;
 | 
						|
	auto textResult = item->_text.originalTextWithEntities(
 | 
						|
		selection,
 | 
						|
		ExpandLinksAll);
 | 
						|
	auto skipped = skipTextSelection(selection);
 | 
						|
	auto mediaDisplayed = (media && media->isDisplayed());
 | 
						|
	auto mediaResult = (mediaDisplayed || isHiddenByGroup())
 | 
						|
		? media->selectedText(skipped)
 | 
						|
		: TextWithEntities();
 | 
						|
	if (auto entry = logEntryOriginal()) {
 | 
						|
		const auto originalSelection = mediaDisplayed
 | 
						|
			? media->skipSelection(skipped)
 | 
						|
			: skipped;
 | 
						|
		logEntryOriginalResult = entry->selectedText(originalSelection);
 | 
						|
	}
 | 
						|
	auto result = textResult;
 | 
						|
	if (result.text.isEmpty()) {
 | 
						|
		result = std::move(mediaResult);
 | 
						|
	} else if (!mediaResult.text.isEmpty()) {
 | 
						|
		result.text += qstr("\n\n");
 | 
						|
		TextUtilities::Append(result, std::move(mediaResult));
 | 
						|
	}
 | 
						|
	if (result.text.isEmpty()) {
 | 
						|
		result = std::move(logEntryOriginalResult);
 | 
						|
	} else if (!logEntryOriginalResult.text.isEmpty()) {
 | 
						|
		result.text += qstr("\n\n");
 | 
						|
		TextUtilities::Append(result, std::move(logEntryOriginalResult));
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
TextSelection Message::adjustSelection(
 | 
						|
		TextSelection selection,
 | 
						|
		TextSelectType type) const {
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
 | 
						|
	auto result = item->_text.adjustSelection(selection, type);
 | 
						|
	auto beforeMediaLength = item->_text.length();
 | 
						|
	if (selection.to <= beforeMediaLength) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
	auto mediaDisplayed = media && media->isDisplayed();
 | 
						|
	if (mediaDisplayed) {
 | 
						|
		auto mediaSelection = unskipTextSelection(
 | 
						|
			media->adjustSelection(skipTextSelection(selection), type));
 | 
						|
		if (selection.from >= beforeMediaLength) {
 | 
						|
			result = mediaSelection;
 | 
						|
		} else {
 | 
						|
			result.to = mediaSelection.to;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	auto beforeEntryLength = beforeMediaLength
 | 
						|
		+ (mediaDisplayed ? media->fullSelectionLength() : 0);
 | 
						|
	if (selection.to <= beforeEntryLength) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
	if (const auto entry = logEntryOriginal()) {
 | 
						|
		auto entrySelection = mediaDisplayed
 | 
						|
			? media->skipSelection(skipTextSelection(selection))
 | 
						|
			: skipTextSelection(selection);
 | 
						|
		auto logEntryOriginalSelection = entry->adjustSelection(entrySelection, type);
 | 
						|
		if (mediaDisplayed) {
 | 
						|
			logEntryOriginalSelection = media->unskipSelection(logEntryOriginalSelection);
 | 
						|
		}
 | 
						|
		logEntryOriginalSelection = unskipTextSelection(logEntryOriginalSelection);
 | 
						|
		if (selection.from >= beforeEntryLength) {
 | 
						|
			result = logEntryOriginalSelection;
 | 
						|
		} else {
 | 
						|
			result.to = logEntryOriginalSelection.to;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void Message::drawInfo(
 | 
						|
		Painter &p,
 | 
						|
		int right,
 | 
						|
		int bottom,
 | 
						|
		int width,
 | 
						|
		bool selected,
 | 
						|
		InfoDisplayType type) const {
 | 
						|
	p.setFont(st::msgDateFont);
 | 
						|
 | 
						|
	bool outbg = hasOutLayout();
 | 
						|
	bool invertedsprites = (type == InfoDisplayType::Image)
 | 
						|
		|| (type == InfoDisplayType::Background);
 | 
						|
	int32 infoRight = right, infoBottom = bottom;
 | 
						|
	switch (type) {
 | 
						|
	case InfoDisplayType::Default:
 | 
						|
		infoRight -= st::msgPadding.right() - st::msgDateDelta.x();
 | 
						|
		infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y();
 | 
						|
		p.setPen(selected
 | 
						|
			? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected)
 | 
						|
			: (outbg ? st::msgOutDateFg : st::msgInDateFg));
 | 
						|
	break;
 | 
						|
	case InfoDisplayType::Image:
 | 
						|
		infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x();
 | 
						|
		infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y();
 | 
						|
		p.setPen(st::msgDateImgFg);
 | 
						|
	break;
 | 
						|
	case InfoDisplayType::Background:
 | 
						|
		infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x();
 | 
						|
		infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y();
 | 
						|
		p.setPen(st::msgServiceFg);
 | 
						|
	break;
 | 
						|
	}
 | 
						|
 | 
						|
	const auto item = message();
 | 
						|
	auto infoW = infoWidth();
 | 
						|
	if (rtl()) infoRight = width - infoRight + infoW;
 | 
						|
 | 
						|
	auto dateX = infoRight - infoW;
 | 
						|
	auto dateY = infoBottom - st::msgDateFont->height;
 | 
						|
	if (type == InfoDisplayType::Image) {
 | 
						|
		auto dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
 | 
						|
		App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
 | 
						|
	} else if (type == InfoDisplayType::Background) {
 | 
						|
		auto dateW = infoW + 2 * st::msgDateImgPadding.x(), dateH = st::msgDateFont->height + 2 * st::msgDateImgPadding.y();
 | 
						|
		App::roundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners);
 | 
						|
	}
 | 
						|
	dateX += timeLeft();
 | 
						|
 | 
						|
	if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
 | 
						|
		msgsigned->signature.drawElided(p, dateX, dateY, item->_timeWidth);
 | 
						|
	} else if (const auto edited = displayedEditBadge()) {
 | 
						|
		edited->text.drawElided(p, dateX, dateY, item->_timeWidth);
 | 
						|
	} else {
 | 
						|
		p.drawText(dateX, dateY + st::msgDateFont->ascent, item->_timeText);
 | 
						|
	}
 | 
						|
 | 
						|
	if (auto views = item->Get<HistoryMessageViews>()) {
 | 
						|
		auto icon = [&] {
 | 
						|
			if (item->id > 0) {
 | 
						|
				if (outbg) {
 | 
						|
					return &(invertedsprites ? st::historyViewsInvertedIcon : (selected ? st::historyViewsOutSelectedIcon : st::historyViewsOutIcon));
 | 
						|
				}
 | 
						|
				return &(invertedsprites ? st::historyViewsInvertedIcon : (selected ? st::historyViewsInSelectedIcon : st::historyViewsInIcon));
 | 
						|
			}
 | 
						|
			return &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon);
 | 
						|
		}();
 | 
						|
		if (item->id > 0) {
 | 
						|
			icon->paint(p, infoRight - infoW, infoBottom + st::historyViewsTop, width);
 | 
						|
			p.drawText(infoRight - infoW + st::historyViewsWidth, infoBottom - st::msgDateFont->descent, views->_viewsText);
 | 
						|
		} else if (!outbg) { // sending outbg icon will be painted below
 | 
						|
			auto iconSkip = st::historyViewsSpace + views->_viewsWidth;
 | 
						|
			icon->paint(p, infoRight - infoW + iconSkip, infoBottom + st::historyViewsTop, width);
 | 
						|
		}
 | 
						|
	} else if (item->id < 0 && item->history()->peer->isSelf() && !outbg) {
 | 
						|
		auto icon = &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon);
 | 
						|
		icon->paint(p, infoRight - infoW, infoBottom + st::historyViewsTop, width);
 | 
						|
	}
 | 
						|
	if (outbg) {
 | 
						|
		auto icon = [&] {
 | 
						|
			if (item->id > 0) {
 | 
						|
				if (item->unread()) {
 | 
						|
					return &(invertedsprites ? st::historySentInvertedIcon : (selected ? st::historySentSelectedIcon : st::historySentIcon));
 | 
						|
				}
 | 
						|
				return &(invertedsprites ? st::historyReceivedInvertedIcon : (selected ? st::historyReceivedSelectedIcon : st::historyReceivedIcon));
 | 
						|
			}
 | 
						|
			return &(invertedsprites ? st::historySendingInvertedIcon : st::historySendingIcon);
 | 
						|
		}();
 | 
						|
		icon->paint(p, QPoint(infoRight, infoBottom) + st::historySendStatePosition, width);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool Message::pointInTime(
 | 
						|
		int right,
 | 
						|
		int bottom,
 | 
						|
		QPoint point,
 | 
						|
		InfoDisplayType type) const {
 | 
						|
	auto infoRight = right;
 | 
						|
	auto infoBottom = bottom;
 | 
						|
	switch (type) {
 | 
						|
	case InfoDisplayType::Default:
 | 
						|
		infoRight -= st::msgPadding.right() - st::msgDateDelta.x();
 | 
						|
		infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y();
 | 
						|
		break;
 | 
						|
	case InfoDisplayType::Image:
 | 
						|
		infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x();
 | 
						|
		infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y();
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	const auto item = message();
 | 
						|
	auto dateX = infoRight - infoWidth() + timeLeft();
 | 
						|
	auto dateY = infoBottom - st::msgDateFont->height;
 | 
						|
	return QRect(
 | 
						|
		dateX,
 | 
						|
		dateY,
 | 
						|
		item->_timeWidth,
 | 
						|
		st::msgDateFont->height).contains(point);
 | 
						|
}
 | 
						|
 | 
						|
int Message::infoWidth() const {
 | 
						|
	const auto item = message();
 | 
						|
	auto result = item->_timeWidth;
 | 
						|
	if (auto views = item->Get<HistoryMessageViews>()) {
 | 
						|
		result += st::historyViewsSpace
 | 
						|
			+ views->_viewsWidth
 | 
						|
			+ st::historyViewsWidth;
 | 
						|
	} else if (item->id < 0 && item->history()->peer->isSelf()) {
 | 
						|
		if (!hasOutLayout()) {
 | 
						|
			result += st::historySendStateSpace;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (hasOutLayout()) {
 | 
						|
		result += st::historySendStateSpace;
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void Message::refreshDataIdHook() {
 | 
						|
	if (base::take(_rightActionLink)) {
 | 
						|
		_rightActionLink = rightActionLink();
 | 
						|
	}
 | 
						|
	if (base::take(_fastReplyLink)) {
 | 
						|
		_fastReplyLink = fastReplyLink();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int Message::timeLeft() const {
 | 
						|
	const auto item = message();
 | 
						|
	auto result = 0;
 | 
						|
	if (auto views = item->Get<HistoryMessageViews>()) {
 | 
						|
		result += st::historyViewsSpace + views->_viewsWidth + st::historyViewsWidth;
 | 
						|
	} else if (item->id < 0 && item->history()->peer->isSelf()) {
 | 
						|
		if (!hasOutLayout()) {
 | 
						|
			result += st::historySendStateSpace;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
int Message::plainMaxWidth() const {
 | 
						|
	return st::msgPadding.left()
 | 
						|
		+ (hasVisibleText() ? message()->_text.maxWidth() : 0)
 | 
						|
		+ st::msgPadding.right();
 | 
						|
}
 | 
						|
 | 
						|
void Message::initLogEntryOriginal() {
 | 
						|
	if (const auto log = message()->Get<HistoryMessageLogEntryOriginal>()) {
 | 
						|
		AddComponents(LogEntryOriginal::Bit());
 | 
						|
		const auto entry = Get<LogEntryOriginal>();
 | 
						|
		entry->page = std::make_unique<HistoryWebPage>(this, log->page);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
HistoryWebPage *Message::logEntryOriginal() const {
 | 
						|
	if (const auto entry = Get<LogEntryOriginal>()) {
 | 
						|
		return entry->page.get();
 | 
						|
	}
 | 
						|
	return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::hasFromName() const {
 | 
						|
	switch (context()) {
 | 
						|
	case Context::AdminLog:
 | 
						|
	case Context::Feed:
 | 
						|
		return true;
 | 
						|
	case Context::History: {
 | 
						|
		const auto item = message();
 | 
						|
		return !hasOutLayout()
 | 
						|
			&& (!item->history()->peer->isUser()
 | 
						|
				|| item->history()->peer->isSelf());
 | 
						|
	} break;
 | 
						|
	case Context::ContactPreview:
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	Unexpected("Context in Message::hasFromPhoto.");
 | 
						|
}
 | 
						|
 | 
						|
bool Message::displayFromName() const {
 | 
						|
	if (!hasFromName()) return false;
 | 
						|
	if (isAttachedToPrevious()) return false;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::displayForwardedFrom() const {
 | 
						|
	const auto item = message();
 | 
						|
	if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
 | 
						|
		if (item->history()->peer->isSelf()) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		const auto media = this->media();
 | 
						|
		return item->Has<HistoryMessageVia>()
 | 
						|
			|| !media
 | 
						|
			|| !media->isDisplayed()
 | 
						|
			|| !media->hideForwardedFrom()
 | 
						|
			|| forwarded->originalSender->isChannel();
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::hasOutLayout() const {
 | 
						|
	const auto item = message();
 | 
						|
	if (item->history()->peer->isSelf()) {
 | 
						|
		return !item->Has<HistoryMessageForwarded>();
 | 
						|
	}
 | 
						|
	return item->out() && !item->isPost();
 | 
						|
}
 | 
						|
 | 
						|
bool Message::drawBubble() const {
 | 
						|
	const auto item = message();
 | 
						|
	if (isHidden()) {
 | 
						|
		return false;
 | 
						|
	} else if (logEntryOriginal()) {
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	const auto media = this->media();
 | 
						|
	return media
 | 
						|
		? (hasVisibleText() || media->needsBubble())
 | 
						|
		: !item->isEmpty();
 | 
						|
}
 | 
						|
 | 
						|
bool Message::hasBubble() const {
 | 
						|
	return drawBubble();
 | 
						|
}
 | 
						|
 | 
						|
bool Message::hasFastReply() const {
 | 
						|
	if (context() != Context::History) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	const auto peer = data()->history()->peer;
 | 
						|
	return !hasOutLayout() && (peer->isChat() || peer->isMegagroup());
 | 
						|
}
 | 
						|
 | 
						|
bool Message::displayFastReply() const {
 | 
						|
	return hasFastReply()
 | 
						|
		&& IsServerMsgId(data()->id)
 | 
						|
		&& data()->history()->peer->canWrite()
 | 
						|
		&& !delegate()->elementInSelectionMode();
 | 
						|
}
 | 
						|
 | 
						|
bool Message::displayRightAction() const {
 | 
						|
	return displayFastShare() || displayGoToOriginal();
 | 
						|
}
 | 
						|
 | 
						|
bool Message::displayFastShare() const {
 | 
						|
	const auto item = message();
 | 
						|
	const auto peer = item->history()->peer;
 | 
						|
	if (!IsServerMsgId(item->id)) {
 | 
						|
		return false;
 | 
						|
	} else if (peer->isChannel()) {
 | 
						|
		return !peer->isMegagroup();
 | 
						|
	} else if (const auto user = peer->asUser()) {
 | 
						|
		if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
 | 
						|
			return !peer->isSelf()
 | 
						|
				&& forwarded->originalSender->isChannel()
 | 
						|
				&& !forwarded->originalSender->isMegagroup();
 | 
						|
		} else if (user->botInfo && !item->out()) {
 | 
						|
			if (const auto media = this->media()) {
 | 
						|
				return media->allowsFastShare();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::displayGoToOriginal() const {
 | 
						|
	const auto item = message();
 | 
						|
	const auto peer = item->history()->peer;
 | 
						|
	if (peer->isSelf()) {
 | 
						|
		if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
 | 
						|
			return forwarded->savedFromPeer && forwarded->savedFromMsgId;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void Message::drawRightAction(
 | 
						|
		Painter &p,
 | 
						|
		int left,
 | 
						|
		int top,
 | 
						|
		int outerWidth) const {
 | 
						|
	p.setPen(Qt::NoPen);
 | 
						|
	p.setBrush(st::msgServiceBg);
 | 
						|
	{
 | 
						|
		PainterHighQualityEnabler hq(p);
 | 
						|
		p.drawEllipse(rtlrect(
 | 
						|
			left,
 | 
						|
			top,
 | 
						|
			st::historyFastShareSize,
 | 
						|
			st::historyFastShareSize,
 | 
						|
			outerWidth));
 | 
						|
	}
 | 
						|
	if (displayFastShare()) {
 | 
						|
		st::historyFastShareIcon.paint(p, left, top, outerWidth);
 | 
						|
	} else {
 | 
						|
		st::historyGoToOriginalIcon.paint(p, left, top, outerWidth);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
ClickHandlerPtr Message::rightActionLink() const {
 | 
						|
	if (!_rightActionLink) {
 | 
						|
		const auto itemId = data()->fullId();
 | 
						|
		const auto forwarded = data()->Get<HistoryMessageForwarded>();
 | 
						|
		const auto savedFromPeer = forwarded ? forwarded->savedFromPeer : nullptr;
 | 
						|
		const auto savedFromMsgId = forwarded ? forwarded->savedFromMsgId : 0;
 | 
						|
		_rightActionLink = std::make_shared<LambdaClickHandler>([=] {
 | 
						|
			if (const auto item = App::histItemById(itemId)) {
 | 
						|
				if (savedFromPeer && savedFromMsgId) {
 | 
						|
					App::wnd()->controller()->showPeerHistory(
 | 
						|
						savedFromPeer,
 | 
						|
						Window::SectionShow::Way::Forward,
 | 
						|
						savedFromMsgId);
 | 
						|
				} else {
 | 
						|
					FastShareMessage(item);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		});
 | 
						|
	}
 | 
						|
	return _rightActionLink;
 | 
						|
}
 | 
						|
 | 
						|
ClickHandlerPtr Message::fastReplyLink() const {
 | 
						|
	if (!_fastReplyLink) {
 | 
						|
		const auto itemId = data()->fullId();
 | 
						|
		_fastReplyLink = std::make_shared<LambdaClickHandler>([=] {
 | 
						|
			if (const auto item = App::histItemById(itemId)) {
 | 
						|
				if (const auto main = App::main()) {
 | 
						|
					main->replyToItem(item);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		});
 | 
						|
	}
 | 
						|
	return _fastReplyLink;
 | 
						|
}
 | 
						|
 | 
						|
void Message::updateMediaInBubbleState() {
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
 | 
						|
	auto mediaHasSomethingBelow = false;
 | 
						|
	auto mediaHasSomethingAbove = false;
 | 
						|
	auto getMediaHasSomethingAbove = [&] {
 | 
						|
		return displayFromName()
 | 
						|
			|| displayForwardedFrom()
 | 
						|
			|| item->Has<HistoryMessageReply>()
 | 
						|
			|| item->Has<HistoryMessageVia>();
 | 
						|
	};
 | 
						|
	auto entry = logEntryOriginal();
 | 
						|
	if (entry) {
 | 
						|
		mediaHasSomethingBelow = true;
 | 
						|
		mediaHasSomethingAbove = getMediaHasSomethingAbove();
 | 
						|
		auto entryState = (mediaHasSomethingAbove
 | 
						|
			|| hasVisibleText()
 | 
						|
			|| (media && media->isDisplayed()))
 | 
						|
			? MediaInBubbleState::Bottom
 | 
						|
			: MediaInBubbleState::None;
 | 
						|
		entry->setInBubbleState(entryState);
 | 
						|
	}
 | 
						|
	if (!media) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	media->updateNeedBubbleState();
 | 
						|
	if (!drawBubble()) {
 | 
						|
		media->setInBubbleState(MediaInBubbleState::None);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!entry) {
 | 
						|
		mediaHasSomethingAbove = getMediaHasSomethingAbove();
 | 
						|
	}
 | 
						|
	if (hasVisibleText()) {
 | 
						|
		mediaHasSomethingAbove = true;
 | 
						|
	}
 | 
						|
	const auto state = [&] {
 | 
						|
		if (mediaHasSomethingAbove) {
 | 
						|
			if (mediaHasSomethingBelow) {
 | 
						|
				return MediaInBubbleState::Middle;
 | 
						|
			}
 | 
						|
			return MediaInBubbleState::Bottom;
 | 
						|
		} else if (mediaHasSomethingBelow) {
 | 
						|
			return MediaInBubbleState::Top;
 | 
						|
		}
 | 
						|
		return MediaInBubbleState::None;
 | 
						|
	}();
 | 
						|
	media->setInBubbleState(state);
 | 
						|
}
 | 
						|
 | 
						|
void Message::fromNameUpdated(int width) const {
 | 
						|
	const auto item = message();
 | 
						|
	const auto replyWidth = hasFastReply()
 | 
						|
		? st::msgFont->width(FastReplyText())
 | 
						|
		: 0;
 | 
						|
	if (item->hasAdminBadge()) {
 | 
						|
		const auto badgeWidth = st::msgFont->width(AdminBadgeText());
 | 
						|
		width -= st::msgPadding.right() + std::max(badgeWidth, replyWidth);
 | 
						|
	} else if (replyWidth) {
 | 
						|
		width -= st::msgPadding.right() + replyWidth;
 | 
						|
	}
 | 
						|
	item->_fromNameVersion = item->displayFrom()->nameVersion;
 | 
						|
	if (const auto via = item->Get<HistoryMessageVia>()) {
 | 
						|
		if (!displayForwardedFrom()) {
 | 
						|
			via->resize(width
 | 
						|
				- st::msgPadding.left()
 | 
						|
				- st::msgPadding.right()
 | 
						|
				- item->displayFrom()->nameText.maxWidth()
 | 
						|
				- st::msgServiceFont->spacew);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
TextSelection Message::skipTextSelection(TextSelection selection) const {
 | 
						|
	return HistoryView::UnshiftItemSelection(selection, message()->_text);
 | 
						|
}
 | 
						|
 | 
						|
TextSelection Message::unskipTextSelection(TextSelection selection) const {
 | 
						|
	return HistoryView::ShiftItemSelection(selection, message()->_text);
 | 
						|
}
 | 
						|
 | 
						|
QRect Message::countGeometry() const {
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
	const auto mediaWidth = (media && media->isDisplayed())
 | 
						|
		? media->width()
 | 
						|
		: width();
 | 
						|
	const auto outbg = hasOutLayout();
 | 
						|
	const auto availableWidth = width()
 | 
						|
		- st::msgMargin.left()
 | 
						|
		- st::msgMargin.right();
 | 
						|
	auto contentLeft = (outbg && !Adaptive::ChatWide())
 | 
						|
		? st::msgMargin.right()
 | 
						|
		: st::msgMargin.left();
 | 
						|
	auto contentWidth = availableWidth;
 | 
						|
	if (hasFromPhoto()) {
 | 
						|
		contentLeft += st::msgPhotoSkip;
 | 
						|
		if (displayRightAction()) {
 | 
						|
			contentWidth -= st::msgPhotoSkip;
 | 
						|
		}
 | 
						|
	//} else if (!Adaptive::Wide() && !out() && !fromChannel() && st::msgPhotoSkip - (hmaxwidth - hwidth) > 0) {
 | 
						|
	//	contentLeft += st::msgPhotoSkip - (hmaxwidth - hwidth);
 | 
						|
	}
 | 
						|
	accumulate_min(contentWidth, maxWidth());
 | 
						|
	accumulate_min(contentWidth, st::msgMaxWidth);
 | 
						|
	if (mediaWidth < contentWidth) {
 | 
						|
		const auto textualWidth = plainMaxWidth();
 | 
						|
		if (mediaWidth < textualWidth) {
 | 
						|
			accumulate_min(contentWidth, textualWidth);
 | 
						|
		} else {
 | 
						|
			contentWidth = mediaWidth;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (contentWidth < availableWidth && outbg && !Adaptive::ChatWide()) {
 | 
						|
		contentLeft += availableWidth - contentWidth;
 | 
						|
	}
 | 
						|
 | 
						|
	const auto contentTop = marginTop();
 | 
						|
	return QRect(
 | 
						|
		contentLeft,
 | 
						|
		contentTop,
 | 
						|
		contentWidth,
 | 
						|
		height() - contentTop - marginBottom());
 | 
						|
}
 | 
						|
 | 
						|
int Message::resizeContentGetHeight(int newWidth) {
 | 
						|
	if (isHidden()) {
 | 
						|
		return marginTop() + marginBottom();
 | 
						|
	} else if (newWidth < st::msgMinWidth) {
 | 
						|
		return height();
 | 
						|
	}
 | 
						|
 | 
						|
	auto newHeight = minHeight();
 | 
						|
 | 
						|
	const auto item = message();
 | 
						|
	const auto media = this->media();
 | 
						|
	const auto mediaDisplayed = media ? media->isDisplayed() : false;
 | 
						|
	const auto bubble = drawBubble();
 | 
						|
 | 
						|
	// This code duplicates countGeometry() but also resizes media.
 | 
						|
	auto contentWidth = newWidth - (st::msgMargin.left() + st::msgMargin.right());
 | 
						|
	if (hasFromPhoto() && displayRightAction()) {
 | 
						|
		contentWidth -= st::msgPhotoSkip;
 | 
						|
	}
 | 
						|
	accumulate_min(contentWidth, maxWidth());
 | 
						|
	accumulate_min(contentWidth, st::msgMaxWidth);
 | 
						|
	if (mediaDisplayed) {
 | 
						|
		media->resizeGetHeight(contentWidth);
 | 
						|
		if (media->width() < contentWidth) {
 | 
						|
			const auto textualWidth = plainMaxWidth();
 | 
						|
			if (media->width() < textualWidth) {
 | 
						|
				accumulate_min(contentWidth, textualWidth);
 | 
						|
			} else {
 | 
						|
				contentWidth = media->width();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (bubble) {
 | 
						|
		auto reply = item->Get<HistoryMessageReply>();
 | 
						|
		auto via = item->Get<HistoryMessageVia>();
 | 
						|
		auto entry = logEntryOriginal();
 | 
						|
 | 
						|
		// Entry page is always a bubble bottom.
 | 
						|
		auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
 | 
						|
		auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
 | 
						|
 | 
						|
		if (contentWidth == maxWidth()) {
 | 
						|
			if (mediaDisplayed) {
 | 
						|
				if (entry) {
 | 
						|
					newHeight += entry->resizeGetHeight(contentWidth);
 | 
						|
				}
 | 
						|
			} else if (entry) {
 | 
						|
				// In case of text-only message it is counted in minHeight already.
 | 
						|
				entry->resizeGetHeight(contentWidth);
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (hasVisibleText()) {
 | 
						|
				auto textWidth = qMax(contentWidth - st::msgPadding.left() - st::msgPadding.right(), 1);
 | 
						|
				if (textWidth != item->_textWidth) {
 | 
						|
					item->_textWidth = textWidth;
 | 
						|
					item->_textHeight = item->_text.countHeight(textWidth);
 | 
						|
				}
 | 
						|
				newHeight = item->_textHeight;
 | 
						|
			} else {
 | 
						|
				newHeight = 0;
 | 
						|
			}
 | 
						|
			if (!mediaOnBottom) {
 | 
						|
				newHeight += st::msgPadding.bottom();
 | 
						|
				if (mediaDisplayed) newHeight += st::mediaInBubbleSkip;
 | 
						|
			}
 | 
						|
			if (!mediaOnTop) {
 | 
						|
				newHeight += st::msgPadding.top();
 | 
						|
				if (mediaDisplayed) newHeight += st::mediaInBubbleSkip;
 | 
						|
				if (entry) newHeight += st::mediaInBubbleSkip;
 | 
						|
			}
 | 
						|
			if (mediaDisplayed) {
 | 
						|
				newHeight += media->height();
 | 
						|
				if (entry) {
 | 
						|
					newHeight += entry->resizeGetHeight(contentWidth);
 | 
						|
				}
 | 
						|
			} else if (entry) {
 | 
						|
				newHeight += entry->resizeGetHeight(contentWidth);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (displayFromName()) {
 | 
						|
			fromNameUpdated(contentWidth);
 | 
						|
			newHeight += st::msgNameFont->height;
 | 
						|
		} else if (via && !displayForwardedFrom()) {
 | 
						|
			via->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right());
 | 
						|
			newHeight += st::msgNameFont->height;
 | 
						|
		}
 | 
						|
 | 
						|
		if (displayForwardedFrom()) {
 | 
						|
			auto forwarded = item->Get<HistoryMessageForwarded>();
 | 
						|
			auto fwdheight = ((forwarded->text.maxWidth() > (contentWidth - st::msgPadding.left() - st::msgPadding.right())) ? 2 : 1) * st::semiboldFont->height;
 | 
						|
			newHeight += fwdheight;
 | 
						|
		}
 | 
						|
 | 
						|
		if (reply) {
 | 
						|
			reply->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right());
 | 
						|
			newHeight += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
 | 
						|
		}
 | 
						|
	} else if (mediaDisplayed) {
 | 
						|
		newHeight = media->height();
 | 
						|
	} else {
 | 
						|
		newHeight = 0;
 | 
						|
	}
 | 
						|
	if (const auto keyboard = item->inlineReplyKeyboard()) {
 | 
						|
		const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
 | 
						|
		newHeight += keyboardHeight;
 | 
						|
		keyboard->resize(contentWidth, keyboardHeight - st::msgBotKbButton.margin);
 | 
						|
	}
 | 
						|
 | 
						|
	newHeight += marginTop() + marginBottom();
 | 
						|
	return newHeight;
 | 
						|
}
 | 
						|
 | 
						|
bool Message::hasVisibleText() const {
 | 
						|
	if (message()->emptyText()) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	const auto media = this->media();
 | 
						|
	return !media || !media->hideMessageText();
 | 
						|
}
 | 
						|
 | 
						|
QSize Message::performCountCurrentSize(int newWidth) {
 | 
						|
	const auto item = message();
 | 
						|
	const auto newHeight = resizeContentGetHeight(newWidth);
 | 
						|
 | 
						|
	const auto keyboard = item->inlineReplyKeyboard();
 | 
						|
	if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
 | 
						|
		const auto oldTop = markup->oldTop;
 | 
						|
		if (oldTop >= 0) {
 | 
						|
			markup->oldTop = -1;
 | 
						|
			if (keyboard) {
 | 
						|
				const auto height = st::msgBotKbButton.margin + keyboard->naturalHeight();
 | 
						|
				const auto keyboardTop = newHeight - height + st::msgBotKbButton.margin - marginBottom();
 | 
						|
				if (keyboardTop != oldTop) {
 | 
						|
					Notify::inlineKeyboardMoved(item, oldTop, keyboardTop);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return { newWidth, newHeight };
 | 
						|
}
 | 
						|
 | 
						|
void Message::refreshEditedBadge() {
 | 
						|
	const auto item = message();
 | 
						|
	const auto edited = displayedEditBadge();
 | 
						|
	const auto editDate = displayedEditDate();
 | 
						|
	const auto dateText = dateTime().toString(cTimeFormat());
 | 
						|
	if (edited) {
 | 
						|
		edited->refresh(dateText, editDate != 0);
 | 
						|
	}
 | 
						|
	if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
 | 
						|
		const auto text = (!edited || !editDate)
 | 
						|
			? dateText
 | 
						|
			: edited->text.originalText();
 | 
						|
		msgsigned->refresh(text);
 | 
						|
	}
 | 
						|
	initTime();
 | 
						|
}
 | 
						|
 | 
						|
void Message::initTime() {
 | 
						|
	const auto item = message();
 | 
						|
	if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
 | 
						|
		item->_timeWidth = msgsigned->maxWidth();
 | 
						|
	} else if (const auto edited = displayedEditBadge()) {
 | 
						|
		item->_timeWidth = edited->maxWidth();
 | 
						|
	} else {
 | 
						|
		item->_timeText = dateTime().toString(cTimeFormat());
 | 
						|
		item->_timeWidth = st::msgDateFont->width(item->_timeText);
 | 
						|
	}
 | 
						|
	if (const auto views = item->Get<HistoryMessageViews>()) {
 | 
						|
		views->_viewsText = (views->_views >= 0)
 | 
						|
			? FormatViewsCount(views->_views)
 | 
						|
			: QString();
 | 
						|
		views->_viewsWidth = views->_viewsText.isEmpty()
 | 
						|
			? 0
 | 
						|
			: st::msgDateFont->width(views->_viewsText);
 | 
						|
	}
 | 
						|
	if (item->_text.hasSkipBlock()) {
 | 
						|
		if (item->_text.updateSkipBlock(skipBlockWidth(), skipBlockHeight())) {
 | 
						|
			item->_textWidth = -1;
 | 
						|
			item->_textHeight = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool Message::displayEditedBadge() const {
 | 
						|
	return (displayedEditDate() != TimeId(0));
 | 
						|
}
 | 
						|
 | 
						|
TimeId Message::displayedEditDate() const {
 | 
						|
	const auto item = message();
 | 
						|
	auto hasViaBotId = item->Has<HistoryMessageVia>();
 | 
						|
	auto hasInlineMarkup = (item->inlineReplyMarkup() != nullptr);
 | 
						|
	return displayedEditDate(hasViaBotId || hasInlineMarkup);
 | 
						|
}
 | 
						|
 | 
						|
TimeId Message::displayedEditDate(
 | 
						|
		bool hasViaBotOrInlineMarkup) const {
 | 
						|
	if (hasViaBotOrInlineMarkup) {
 | 
						|
		return TimeId(0);
 | 
						|
	} else if (const auto fromUser = message()->from()->asUser()) {
 | 
						|
		if (fromUser->botInfo) {
 | 
						|
			return TimeId(0);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (const auto edited = displayedEditBadge()) {
 | 
						|
		return edited->date;
 | 
						|
	}
 | 
						|
	return TimeId(0);
 | 
						|
}
 | 
						|
 | 
						|
HistoryMessageEdited *Message::displayedEditBadge() {
 | 
						|
	if (const auto media = this->media()) {
 | 
						|
		if (media->overrideEditedDate()) {
 | 
						|
			return media->displayedEditBadge();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return message()->Get<HistoryMessageEdited>();
 | 
						|
}
 | 
						|
 | 
						|
const HistoryMessageEdited *Message::displayedEditBadge() const {
 | 
						|
	if (const auto media = this->media()) {
 | 
						|
		if (media->overrideEditedDate()) {
 | 
						|
			return media->displayedEditBadge();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return message()->Get<HistoryMessageEdited>();
 | 
						|
}
 | 
						|
 | 
						|
} // namespace HistoryView
 |