Move message components to history_item_components.

Also fix channel signatures rendering.
This commit is contained in:
John Preston 2017-12-18 19:44:50 +04:00
parent 16ca2d39c5
commit 92333e982c
28 changed files with 1941 additions and 1638 deletions

View File

@ -33,6 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "boxes/add_contact_box.h"
#include "history/history_message.h"
#include "history/history_item_components.h"
#include "storage/localstorage.h"
#include "auth_session.h"
#include "boxes/confirm_box.h"

View File

@ -34,6 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_service_layout.h"
#include "history/history_location_manager.h"
#include "history/history_media_types.h"
#include "history/history_item_components.h"
#include "media/media_audio.h"
#include "inline_bots/inline_bot_layout_item.h"
#include "messenger.h"

View File

@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/localstorage.h"
#include "storage/storage_facade.h"
#include "storage/serialize_common.h"
#include "history/history_item_components.h"
#include "window/notifications_manager.h"
#include "platform/platform_specific.h"
#include "calls/calls_instance.h"

View File

@ -20,9 +20,91 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "chat_helpers/bot_keyboard.h"
#include "history/history_item_components.h"
#include "styles/style_widgets.h"
#include "styles/style_history.h"
namespace {
class Style : public ReplyKeyboard::Style {
public:
Style(
not_null<BotKeyboard*> parent,
const style::BotKeyboardButton &st);
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;
private:
not_null<BotKeyboard*> _parent;
};
Style::Style(
not_null<BotKeyboard*> parent,
const style::BotKeyboardButton &st)
: ReplyKeyboard::Style(st), _parent(parent) {
}
void Style::startPaint(Painter &p) const {
p.setPen(st::botKbColor);
p.setFont(st::botKbStyle.font);
}
const style::TextStyle &Style::textStyle() const {
return st::botKbStyle;
}
void Style::repaint(not_null<const HistoryItem*> item) const {
_parent->update();
}
int Style::buttonRadius() const {
return st::buttonRadius;
}
void Style::paintButtonBg(
Painter &p,
const QRect &rect,
float64 howMuchOver) const {
App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners);
}
void Style::paintButtonIcon(
Painter &p,
const QRect &rect,
int outerWidth,
HistoryMessageMarkupButton::Type type) const {
// Buttons with icons should not appear here.
}
void Style::paintButtonLoading(Painter &p, const QRect &rect) const {
// Buttons with loading progress should not appear here.
}
int Style::minButtonWidth(HistoryMessageMarkupButton::Type type) const {
int result = 2 * buttonPadding();
return result;
}
} // namespace
BotKeyboard::BotKeyboard(QWidget *parent) : TWidget(parent)
, _st(&st::botKbButton) {
setGeometry(0, 0, _st->margin, st::botKbScroll.deltat);
@ -43,40 +125,6 @@ void BotKeyboard::paintEvent(QPaintEvent *e) {
}
}
void BotKeyboard::Style::startPaint(Painter &p) const {
p.setPen(st::botKbColor);
p.setFont(st::botKbStyle.font);
}
const style::TextStyle &BotKeyboard::Style::textStyle() const {
return st::botKbStyle;
}
void BotKeyboard::Style::repaint(not_null<const HistoryItem*> item) const {
_parent->update();
}
int BotKeyboard::Style::buttonRadius() const {
return st::buttonRadius;
}
void BotKeyboard::Style::paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const {
App::roundRect(p, rect, st::botKbBg, BotKeyboardCorners);
}
void BotKeyboard::Style::paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const {
// Buttons with icons should not appear here.
}
void BotKeyboard::Style::paintButtonLoading(Painter &p, const QRect &rect) const {
// Buttons with loading progress should not appear here.
}
int BotKeyboard::Style::minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const {
int result = 2 * buttonPadding();
return result;
}
void BotKeyboard::mousePressEvent(QMouseEvent *e) {
_lastMousePos = e->globalPos();
updateSelected();
@ -250,3 +298,5 @@ void BotKeyboard::updateSelected() {
setCursor(link ? style::cur_pointer : style::cur_default);
}
}
BotKeyboard::~BotKeyboard() = default;

View File

@ -22,7 +22,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/tooltip.h"
class BotKeyboard : public TWidget, public Ui::AbstractTooltipShower, public ClickHandlerHost {
class ReplyKeyboard;
class BotKeyboard
: public TWidget
, public Ui::AbstractTooltipShower
, public ClickHandlerHost {
Q_OBJECT
public:
@ -57,6 +62,8 @@ public:
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
~BotKeyboard();
protected:
int resizeGetHeight(int newWidth) override;
@ -83,27 +90,6 @@ private:
QPoint _lastMousePos;
std::unique_ptr<ReplyKeyboard> _impl;
class Style : public ReplyKeyboard::Style {
public:
Style(BotKeyboard *parent, const style::BotKeyboardButton &st) : ReplyKeyboard::Style(st), _parent(parent) {
}
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, HistoryMessageReplyMarkup::Button::Type type) const override;
void paintButtonLoading(Painter &p, const QRect &rect) const override;
int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const override;
private:
BotKeyboard *_parent;
};
const style::BotKeyboardButton *_st = nullptr;
};

View File

@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "core/click_handler_types.h"
#include "media/media_clip_reader.h"
#include "window/window_controller.h"
#include "history/history_item_components.h"
#include "observer_peer.h"
#include "mainwindow.h"
#include "mainwidget.h"
@ -69,19 +70,22 @@ bool insertBotCommand(const QString &cmd) {
return false;
}
void activateBotCommand(const HistoryItem *msg, int row, int col) {
const HistoryMessageReplyMarkup::Button *button = nullptr;
void activateBotCommand(
not_null<const HistoryItem*> msg,
int row,
int column) {
const HistoryMessageMarkupButton *button = nullptr;
if (auto markup = msg->Get<HistoryMessageReplyMarkup>()) {
if (row < markup->rows.size()) {
auto &buttonRow = markup->rows[row];
if (col < buttonRow.size()) {
button = &buttonRow.at(col);
if (column < buttonRow.size()) {
button = &buttonRow[column];
}
}
}
if (!button) return;
using ButtonType = HistoryMessageReplyMarkup::Button::Type;
using ButtonType = HistoryMessageMarkupButton::Type;
switch (button->type) {
case ButtonType::Default: {
// Copy string before passing it to the sending method
@ -93,7 +97,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) {
case ButtonType::Callback:
case ButtonType::Game: {
if (auto m = main()) {
m->app_sendBotCallback(button, msg, row, col);
m->app_sendBotCallback(button, msg, row, column);
}
} break;

View File

@ -130,11 +130,21 @@ inline auto LambdaDelayedOnce(
};
}
void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo = 0);
void sendBotCommand(
PeerData *peer,
UserData *bot,
const QString &cmd,
MsgId replyTo = 0);
bool insertBotCommand(const QString &cmd);
void activateBotCommand(const HistoryItem *msg, int row, int col);
void activateBotCommand(
not_null<const HistoryItem*> msg,
int row,
int column);
void searchByHashtag(const QString &tag, PeerData *inPeer);
void openPeerByName(const QString &username, MsgId msgId = ShowAtUnreadMsgId, const QString &startToken = QString());
void openPeerByName(
const QString &username,
MsgId msgId = ShowAtUnreadMsgId,
const QString &startToken = QString());
void joinGroupByHash(const QString &hash);
void removeDialog(History *history);
void showSettings();

View File

@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_message.h"
#include "history/history_media_types.h"
#include "history/history_service.h"
#include "history/history_item_components.h"
#include "dialogs/dialogs_indexed_list.h"
#include "styles/style_dialogs.h"
#include "data/data_drafts.h"
@ -679,7 +680,7 @@ void checkForSwitchInlineButton(HistoryItem *item) {
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
for_const (auto &row, markup->rows) {
for_const (auto &button, row) {
if (button.type == HistoryMessageReplyMarkup::Button::Type::SwitchInline) {
if (button.type == HistoryMessageMarkupButton::Type::SwitchInline) {
Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data));
return;
}

View File

@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_service_layout.h"
#include "history/history_admin_log_section.h"
#include "history/history_admin_log_filter.h"
#include "history/history_item_components.h"
#include "chat_helpers/message_field.h"
#include "mainwindow.h"
#include "mainwidget.h"
@ -424,17 +425,17 @@ void InnerWidget::updateEmptyText() {
QString InnerWidget::tooltipText() const {
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
if (auto item = App::hoveredItem()) {
if (const auto item = App::hoveredItem()) {
auto dateText = item->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
return dateText;
}
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
if (auto item = App::hoveredItem()) {
if (auto forwarded = item->Get<HistoryMessageForwarded>()) {
return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
if (const auto item = App::hoveredItem()) {
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
return forwarded->text.originalText(AllTextSelection, ExpandLinksNone);
}
}
} else if (auto lnk = ClickHandler::getActive()) {
} else if (const auto lnk = ClickHandler::getActive()) {
return lnk->tooltip();
}
return QString();

View File

@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_message.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_item_components.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_peer_menu.h"
@ -2774,21 +2775,22 @@ void HistoryInner::applyDragSelection(
QString HistoryInner::tooltipText() const {
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
if (App::hoveredItem()) {
auto dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
auto editedDate = App::hoveredItem()->displayedEditDate();
if (const auto item = App::hoveredItem()) {
auto dateText = item->date.toString(
QLocale::system().dateTimeFormat(QLocale::LongFormat));
auto editedDate = item->displayedEditDate();
if (!editedDate.isNull()) {
dateText += '\n' + lng_edited_date(lt_date, editedDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
dateText += '\n' + lng_forwarded_date(lt_date, forwarded->_originalDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
dateText += '\n' + lng_forwarded_date(lt_date, forwarded->originalDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
return dateText;
}
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
if (App::hoveredItem()) {
if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
if (const auto item = App::hoveredItem()) {
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
return forwarded->text.originalText(AllTextSelection, ExpandLinksNone);
}
}
} else if (auto lnk = ClickHandler::getActive()) {

View File

@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "history/history_item_components.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_media_grouped.h"
@ -69,566 +70,6 @@ HistoryTextState::HistoryTextState(
, link(link) {
}
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 == HistoryMessageReplyMarkup::Button::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 == HistoryMessageReplyMarkup::Button::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 HistoryMessageReplyMarkup::Button *ReplyMarkupClickHandler::getButton() const {
if (auto item = App::histItemById(_itemId)) {
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
if (_row < markup->rows.size()) {
auto &row = markup->rows[_row];
if (_column < row.size()) {
return &row[_column];
}
}
}
}
return nullptr;
}
void ReplyMarkupClickHandler::onClickImpl() const {
if (auto item = App::histItemById(_itemId)) {
App::activateBotCommand(item, _row, _column);
}
}
// Returns the full text of the corresponding button.
QString ReplyMarkupClickHandler::buttonText() const {
if (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<const HistoryItem*> item,
std::unique_ptr<Style> &&s)
: _item(item)
, _a_selected(animation(this, &ReplyKeyboard::step_selected))
, _st(std::move(s)) {
if (const auto markup = _item->Get<HistoryMessageReplyMarkup>()) {
const auto context = _item->fullId();
const auto rowCount = int(markup->rows.size());
_rows.reserve(rowCount);
for (auto i = 0; i != rowCount; ++i) {
const auto &row = markup->rows.at(i);
const auto rowSize = int(row.size());
auto newRow = std::vector<Button>();
newRow.reserve(rowSize);
for (auto j = 0; j != rowSize; ++j) {
auto button = Button();
const auto text = row[j].text;
button.type = row.at(j).type;
button.link = std::make_shared<ReplyMarkupClickHandler>(
i,
j,
context);
button.text.setText(
_st->textStyle(),
TextUtilities::SingleLine(text),
_textPlainOptions);
button.characters = text.isEmpty() ? 1 : text.size();
newRow.push_back(std::move(button));
}
_rows.push_back(std::move(newRow));
}
}
}
void ReplyKeyboard::updateMessageId() {
const auto msgId = _item->fullId();
for (const auto &row : _rows) {
for (const auto &button : row) {
button.link->setMessageId(msgId);
}
}
}
void ReplyKeyboard::resize(int width, int height) {
_width = width;
auto markup = _item->Get<HistoryMessageReplyMarkup>();
auto y = 0.;
auto buttonHeight = _rows.empty()
? float64(_st->buttonHeight())
: (float64(height + _st->buttonSkip()) / _rows.size());
for (auto &row : _rows) {
int s = row.size();
int widthForButtons = _width - ((s - 1) * _st->buttonSkip());
int widthForText = widthForButtons;
int widthOfText = 0;
int maxMinButtonWidth = 0;
for_const (auto &button, row) {
widthOfText += qMax(button.text.maxWidth(), 1);
int minButtonWidth = _st->minButtonWidth(button.type);
widthForText -= minButtonWidth;
accumulate_max(maxMinButtonWidth, minButtonWidth);
}
bool exact = (widthForText == widthOfText);
bool enough = (widthForButtons - s * maxMinButtonWidth) >= widthOfText;
float64 x = 0;
for (Button &button : row) {
int buttonw = qMax(button.text.maxWidth(), 1);
float64 textw = buttonw, minw = _st->minButtonWidth(button.type);
float64 w = textw;
if (exact) {
w += minw;
} else if (enough) {
w = (widthForButtons / float64(s));
textw = w - minw;
} else {
textw = (widthForText / float64(s));
w = minw + textw;
accumulate_max(w, 2 * float64(_st->buttonPadding()));
}
int rectx = static_cast<int>(std::floor(x));
int rectw = static_cast<int>(std::floor(x + w)) - rectx;
button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip()));
if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width());
x += w + _st->buttonSkip();
button.link->setFullDisplayed(textw >= buttonw);
}
y += buttonHeight;
}
}
bool ReplyKeyboard::isEnoughSpace(int width, const style::BotKeyboardButton &st) const {
for_const (auto &row, _rows) {
int s = row.size();
int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding);
for_const (auto &button, row) {
widthLeft -= qMax(button.text.maxWidth(), 1);
if (widthLeft < 0) {
if (row.size() > 3) {
return false;
} else {
break;
}
}
}
}
return true;
}
void ReplyKeyboard::setStyle(std::unique_ptr<Style> &&st) {
_st = std::move(st);
}
int ReplyKeyboard::naturalWidth() const {
auto result = 0;
for (const auto &row : _rows) {
auto maxMinButtonWidth = 0;
for (const auto &button : row) {
accumulate_max(
maxMinButtonWidth,
_st->minButtonWidth(button.type));
}
auto rowMaxButtonWidth = 0;
for (const auto &button : row) {
accumulate_max(
rowMaxButtonWidth,
qMax(button.text.maxWidth(), 1) + maxMinButtonWidth);
}
const auto rowSize = int(row.size());
accumulate_max(
result,
rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip());
}
return result;
}
int ReplyKeyboard::naturalHeight() const {
return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight();
}
void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const {
Assert(_st != nullptr);
Assert(_width > 0);
_st->startPaint(p);
for_const (auto &row, _rows) {
for_const (auto &button, row) {
QRect rect(button.rect);
if (rect.y() >= clip.y() + clip.height()) return;
if (rect.y() + rect.height() < clip.y()) continue;
// just ignore the buttons that didn't layout well
if (rect.x() + rect.width() > _width) break;
_st->paintButton(p, outerWidth, button, ms);
}
}
}
ClickHandlerPtr ReplyKeyboard::getState(QPoint point) const {
Assert(_width > 0);
for_const (auto &row, _rows) {
for_const (auto &button, row) {
QRect rect(button.rect);
// just ignore the buttons that didn't layout well
if (rect.x() + rect.width() > _width) break;
if (rect.contains(point)) {
_savedCoords = point;
return button.link;
}
}
}
return ClickHandlerPtr();
}
void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (!p) return;
_savedActive = active ? p : ClickHandlerPtr();
auto coords = findButtonCoordsByClickHandler(p);
if (coords.i >= 0 && _savedPressed != p) {
startAnimation(coords.i, coords.j, active ? 1 : -1);
}
}
ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) {
for (int i = 0, rows = _rows.size(); i != rows; ++i) {
auto &row = _rows[i];
for (int j = 0, cols = row.size(); j != cols; ++j) {
if (row[j].link == p) {
return { i, j };
}
}
}
return { -1, -1 };
}
void ReplyKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
if (!p) return;
_savedPressed = pressed ? p : ClickHandlerPtr();
auto coords = findButtonCoordsByClickHandler(p);
if (coords.i >= 0) {
auto &button = _rows[coords.i][coords.j];
if (pressed) {
if (!button.ripple) {
auto mask = Ui::RippleAnimation::roundRectMask(
button.rect.size(),
_st->buttonRadius());
button.ripple = std::make_unique<Ui::RippleAnimation>(
_st->_st->ripple,
std::move(mask),
[this] { _st->repaint(_item); });
}
button.ripple->add(_savedCoords - button.rect.topLeft());
} else {
if (button.ripple) {
button.ripple->lastStop();
}
if (_savedActive != p) {
startAnimation(coords.i, coords.j, -1);
}
}
}
}
void ReplyKeyboard::startAnimation(int i, int j, int direction) {
auto notStarted = _animations.empty();
int indexForAnimation = (i * MatrixRowShift + j + 1) * direction;
_animations.remove(-indexForAnimation);
if (!_animations.contains(indexForAnimation)) {
_animations.emplace(indexForAnimation, getms());
}
if (notStarted && !_a_selected.animating()) {
_a_selected.start();
}
}
void ReplyKeyboard::step_selected(TimeMs ms, bool timer) {
for (auto i = _animations.begin(); i != _animations.end();) {
const auto index = std::abs(i->first) - 1;
const auto row = (index / MatrixRowShift);
const auto col = index % MatrixRowShift;
const auto dt = float64(ms - i->second) / st::botKbDuration;
if (dt >= 1) {
_rows[row][col].howMuchOver = (i->first > 0) ? 1 : 0;
i = _animations.erase(i);
} else {
_rows[row][col].howMuchOver = (i->first > 0) ? dt : (1 - dt);
++i;
}
}
if (timer) _st->repaint(_item);
if (_animations.empty()) {
_a_selected.stop();
}
}
void ReplyKeyboard::clearSelection() {
for (const auto [relativeIndex, time] : _animations) {
const auto index = std::abs(relativeIndex) - 1;
const auto row = (index / MatrixRowShift);
const auto col = index % MatrixRowShift;
_rows[row][col].howMuchOver = 0;
}
_animations.clear();
_a_selected.stop();
}
int ReplyKeyboard::Style::buttonSkip() const {
return _st->margin;
}
int ReplyKeyboard::Style::buttonPadding() const {
return _st->padding;
}
int ReplyKeyboard::Style::buttonHeight() const {
return _st->height;
}
void ReplyKeyboard::Style::paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const {
const QRect &rect = button.rect;
paintButtonBg(p, rect, button.howMuchOver);
if (button.ripple) {
button.ripple->paint(p, rect.x(), rect.y(), outerWidth, ms);
if (button.ripple->empty()) {
button.ripple.reset();
}
}
paintButtonIcon(p, rect, outerWidth, button.type);
if (button.type == HistoryMessageReplyMarkup::Button::Type::Callback
|| button.type == HistoryMessageReplyMarkup::Button::Type::Game) {
if (auto data = button.link->getButton()) {
if (data->requestId) {
paintButtonLoading(p, rect);
}
}
}
int tx = rect.x(), tw = rect.width();
if (tw >= st::botKbStyle.font->elidew + _st->padding * 2) {
tx += _st->padding;
tw -= _st->padding * 2;
} else if (tw > st::botKbStyle.font->elidew) {
tx += (tw - st::botKbStyle.font->elidew) / 2;
tw = st::botKbStyle.font->elidew;
}
button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top);
}
void HistoryMessageReplyMarkup::createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v) {
if (v.isEmpty()) {
rows.clear();
return;
}
rows.reserve(v.size());
for_const (auto &row, v) {
switch (row.type()) {
case mtpc_keyboardButtonRow: {
auto &r = row.c_keyboardButtonRow();
auto &b = r.vbuttons.v;
if (!b.isEmpty()) {
auto buttonRow = std::vector<Button>();
buttonRow.reserve(b.size());
for_const (auto &button, b) {
switch (button.type()) {
case mtpc_keyboardButton: {
buttonRow.push_back({ Button::Type::Default, qs(button.c_keyboardButton().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonCallback: {
auto &buttonData = button.c_keyboardButtonCallback();
buttonRow.push_back({ Button::Type::Callback, qs(buttonData.vtext), qba(buttonData.vdata), 0 });
} break;
case mtpc_keyboardButtonRequestGeoLocation: {
buttonRow.push_back({ Button::Type::RequestLocation, qs(button.c_keyboardButtonRequestGeoLocation().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonRequestPhone: {
buttonRow.push_back({ Button::Type::RequestPhone, qs(button.c_keyboardButtonRequestPhone().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonUrl: {
auto &buttonData = button.c_keyboardButtonUrl();
buttonRow.push_back({ Button::Type::Url, qs(buttonData.vtext), qba(buttonData.vurl), 0 });
} break;
case mtpc_keyboardButtonSwitchInline: {
auto &buttonData = button.c_keyboardButtonSwitchInline();
auto buttonType = buttonData.is_same_peer() ? Button::Type::SwitchInlineSame : Button::Type::SwitchInline;
buttonRow.push_back({ buttonType, qs(buttonData.vtext), qba(buttonData.vquery), 0 });
if (buttonType == Button::Type::SwitchInline) {
// Optimization flag.
// Fast check on all new messages if there is a switch button to auto-click it.
flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button;
}
} break;
case mtpc_keyboardButtonGame: {
auto &buttonData = button.c_keyboardButtonGame();
buttonRow.push_back({ Button::Type::Game, qs(buttonData.vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonBuy: {
auto &buttonData = button.c_keyboardButtonBuy();
buttonRow.push_back({ Button::Type::Buy, qs(buttonData.vtext), QByteArray(), 0 });
}
}
}
if (!buttonRow.empty()) {
rows.push_back(std::move(buttonRow));
}
}
} break;
}
}
}
void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) {
flags = 0;
rows.clear();
inlineKeyboard = nullptr;
switch (markup.type()) {
case mtpc_replyKeyboardMarkup: {
auto &d = markup.c_replyKeyboardMarkup();
flags = d.vflags.v;
createFromButtonRows(d.vrows.v);
} break;
case mtpc_replyInlineMarkup: {
auto &d = markup.c_replyInlineMarkup();
flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline;
createFromButtonRows(d.vrows.v);
} break;
case mtpc_replyKeyboardHide: {
auto &d = markup.c_replyKeyboardHide();
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero;
} break;
case mtpc_replyKeyboardForceReply: {
auto &d = markup.c_replyKeyboardForceReply();
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply;
} break;
}
}
void HistoryMessageReplyMarkup::create(
const HistoryMessageReplyMarkup &markup) {
flags = markup.flags;
inlineKeyboard = nullptr;
rows.clear();
for (const auto &row : markup.rows) {
auto buttonRow = std::vector<Button>();
buttonRow.reserve(row.size());
for (const auto &button : row) {
buttonRow.push_back({ button.type, button.text, button.data, 0 });
}
if (!buttonRow.empty()) {
rows.push_back(std::move(buttonRow));
}
}
}
void HistoryMessageUnreadBar::init(int count) {
if (_freezed) return;
_text = lng_unread_bar(lt_count, count);
_width = st::semiboldFont->width(_text);
}
int HistoryMessageUnreadBar::height() {
return st::historyUnreadBarHeight + st::historyUnreadBarMargin;
}
int HistoryMessageUnreadBar::marginTop() {
return st::lineWidth + st::historyUnreadBarMargin;
}
void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const {
p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::historyUnreadBarBg);
p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::historyUnreadBarBorder);
p.setFont(st::historyUnreadBarFont);
p.setPen(st::historyUnreadBarFg);
int left = st::msgServiceMargin.left();
int maxwidth = w;
if (Adaptive::ChatWide()) {
maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
w = maxwidth;
p.drawText((w - _width) / 2, y + marginTop() + (st::historyUnreadBarHeight - 2 * st::lineWidth - st::historyUnreadBarFont->height) / 2 + st::historyUnreadBarFont->ascent, _text);
}
void HistoryMessageDate::init(const QDateTime &date) {
_text = langDayOfMonthFull(date.date());
_width = st::msgServiceFont->width(_text);
}
int HistoryMessageDate::height() const {
return st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom() + st::msgServiceMargin.bottom();
}
void HistoryMessageDate::paint(Painter &p, int y, int w) const {
HistoryLayout::ServiceMessagePainter::paintDate(p, _text, _width, y, w);
}
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default;
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other) : _page(std::move(other._page)) {
}
HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=(HistoryMessageLogEntryOriginal &&other) {
_page = std::move(other._page);
return *this;
}
HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
HistoryMediaPtr::HistoryMediaPtr() = default;
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer)
@ -714,6 +155,22 @@ void HistoryItem::finishEdition(int oldKeyboardTop) {
App::historyUpdateDependent(this);
}
HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return markup;
}
}
return nullptr;
}
ReplyKeyboard *HistoryItem::inlineReplyKeyboard() {
if (const auto markup = inlineReplyMarkup()) {
return markup->inlineKeyboard.get();
}
return nullptr;
}
void HistoryItem::invalidateChatsListEntry() {
if (App::main()) {
App::main()->dlgUpdated(history()->peer, id);
@ -770,6 +227,31 @@ void HistoryItem::markMediaRead() {
}
}
bool HistoryItem::definesReplyKeyboard() const {
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return false;
}
return true;
}
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return (_flags & MTPDmessage::Flag::f_reply_markup);
}
MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const {
Expects(definesReplyKeyboard());
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
return markup->flags;
}
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0;
}
void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->inlineKeyboard) {
@ -798,6 +280,13 @@ void HistoryItem::addLogEntryOriginal(WebPageId localId, const QString &label, c
original->_page = std::make_unique<HistoryWebPage>(this, webpage);
}
UserData *HistoryItem::viaBot() const {
if (const auto via = Get<HistoryMessageVia>()) {
return via->bot;
}
return nullptr;
}
void HistoryItem::destroy() {
if (isLogEntry()) {
Assert(detached());
@ -1122,6 +611,53 @@ QString HistoryItem::directLink() const {
return QString();
}
MsgId HistoryItem::replyToId() const {
if (auto reply = Get<HistoryMessageReply>()) {
return reply->replyToId();
}
return 0;
}
QDateTime HistoryItem::dateOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalDate;
}
return date;
}
PeerData *HistoryItem::senderOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalSender;
}
const auto peer = history()->peer;
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
}
PeerData *HistoryItem::fromOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
if (const auto user = forwarded->originalSender->asUser()) {
return user;
}
}
return from();
}
QString HistoryItem::authorOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalAuthor;
} else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
return msgsigned->author;
}
return QString();
}
MsgId HistoryItem::idOriginal() const {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalId;
}
return id;
}
bool HistoryItem::hasOutLayout() const {
if (history()->peer->isSelf()) {
return !Has<HistoryMessageForwarded>();
@ -1205,11 +741,19 @@ void HistoryItem::setUnreadBarCount(int count) {
void HistoryItem::setUnreadBarFreezed() {
Expects(!isLogEntry());
if (auto bar = Get<HistoryMessageUnreadBar>()) {
if (const auto bar = Get<HistoryMessageUnreadBar>()) {
bar->_freezed = true;
}
}
MessageGroupId HistoryItem::groupId() const {
if (const auto group = Get<HistoryMessageGroup>()) {
return group->groupId;
}
return MessageGroupId::None;
}
bool HistoryItem::groupIdValidityChanged() {
if (Has<HistoryMessageGroup>()) {
if (_media && _media->canBeGrouped()) {
@ -1284,6 +828,13 @@ void HistoryItem::resetGroupMedia(
setPendingInitDimensions();
}
int HistoryItem::displayedDateHeight() const {
if (auto date = Get<HistoryMessageDate>()) {
return date->height();
}
return 0;
}
int HistoryItem::marginTop() const {
auto result = 0;
if (!isHiddenByGroup()) {
@ -1300,6 +851,16 @@ int HistoryItem::marginTop() const {
return result;
}
bool HistoryItem::displayDate() const {
return Has<HistoryMessageDate>();
}
bool HistoryItem::isEmpty() const {
return _text.isEmpty()
&& !_media
&& !Has<HistoryMessageLogEntryOriginal>();
}
int HistoryItem::marginBottom() const {
return isHiddenByGroup() ? 0 : st::msgMargin.bottom();
}

View File

@ -24,6 +24,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/flags.h"
#include "base/value_ordering.h"
struct MessageGroupId;
struct HistoryMessageGroup;
struct HistoryMessageReplyMarkup;
class ReplyKeyboard;
class HistoryMessage;
class HistoryMedia;
namespace base {
template <typename Enum>
class enum_mask;
@ -68,8 +75,6 @@ protected:
};
class HistoryMessage;
enum HistoryCursorState {
HistoryDefaultCursorState,
HistoryInTextCursorState,
@ -123,371 +128,8 @@ enum InfoDisplayType {
InfoDisplayOverBackground,
};
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia> {
void create(int32 userId);
void resize(int32 availw) const;
UserData *_bot = nullptr;
mutable QString _text;
mutable int _width = 0;
mutable int _maxWidth = 0;
ClickHandlerPtr _lnk;
};
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews> {
QString _viewsText;
int _views = 0;
int _viewsWidth = 0;
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned> {
void create(const QString &author, const QString &date);
int maxWidth() const;
QString _author;
Text _signature;
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited> {
void refresh(const QString &date, bool displayed);
int maxWidth() const;
QDateTime date;
Text text;
};
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded> {
void create(const HistoryMessageVia *via) const;
QDateTime _originalDate;
PeerData *_originalSender = nullptr;
QString _originalAuthor;
MsgId _originalId = 0;
mutable Text _text = { 1 };
PeerData *_savedFromPeer = nullptr;
MsgId _savedFromMsgId = 0;
};
struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply> {
HistoryMessageReply() = default;
HistoryMessageReply(const HistoryMessageReply &other) = delete;
HistoryMessageReply(HistoryMessageReply &&other) = delete;
HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete;
HistoryMessageReply &operator=(HistoryMessageReply &&other) {
replyToMsgId = other.replyToMsgId;
std::swap(replyToMsg, other.replyToMsg);
replyToLnk = std::move(other.replyToLnk);
replyToName = std::move(other.replyToName);
replyToText = std::move(other.replyToText);
replyToVersion = other.replyToVersion;
_maxReplyWidth = other._maxReplyWidth;
_replyToVia = std::move(other._replyToVia);
return *this;
}
~HistoryMessageReply() {
// clearData() should be called by holder
Expects(replyToMsg == nullptr);
Expects(_replyToVia == nullptr);
}
bool updateData(HistoryMessage *holder, bool force = false);
void clearData(HistoryMessage *holder); // must be called before destructor
bool isNameUpdated() const;
void updateName() const;
void resize(int width) const;
void itemRemoved(HistoryMessage *holder, HistoryItem *removed);
enum class PaintFlag {
InBubble = (1 << 0),
Selected = (1 << 1),
};
using PaintFlags = base::flags<PaintFlag>;
friend inline constexpr auto is_flag_type(PaintFlag) { return true; };
void paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const;
MsgId replyToId() const {
return replyToMsgId;
}
int replyToWidth() const {
return _maxReplyWidth;
}
ClickHandlerPtr replyToLink() const {
return replyToLnk;
}
MsgId replyToMsgId = 0;
HistoryItem *replyToMsg = nullptr;
ClickHandlerPtr replyToLnk;
mutable Text replyToName, replyToText;
mutable int replyToVersion = 0;
mutable int _maxReplyWidth = 0;
std::unique_ptr<HistoryMessageVia> _replyToVia;
int toWidth = 0;
};
class ReplyKeyboard;
struct HistoryMessageReplyMarkup : public RuntimeComponent<HistoryMessageReplyMarkup> {
HistoryMessageReplyMarkup() = default;
HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) {
}
void create(const MTPReplyMarkup &markup);
void create(const HistoryMessageReplyMarkup &markup);
struct Button {
enum class Type {
Default,
Url,
Callback,
RequestPhone,
RequestLocation,
SwitchInline,
SwitchInlineSame,
Game,
Buy,
};
Type type;
QString text;
QByteArray data;
mutable mtpRequestId requestId;
};
std::vector<std::vector<Button>> rows;
MTPDreplyKeyboardMarkup::Flags flags = 0;
std::unique_ptr<ReplyKeyboard> inlineKeyboard;
// If >= 0 it holds the y coord of the inlineKeyboard before the last edition.
int oldTop = -1;
private:
void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v);
};
class ReplyMarkupClickHandler : public LeftButtonClickHandler {
public:
ReplyMarkupClickHandler(int row, int column, FullMsgId context);
QString tooltip() const override {
return _fullDisplayed ? QString() : buttonText();
}
void setFullDisplayed(bool full) {
_fullDisplayed = full;
}
// Copy to clipboard support.
void copyToClipboard() const override;
QString copyToClipboardContextItemText() const override;
// Finds the corresponding button in the items markup struct.
// If the button is not found it returns nullptr.
// Note: it is possible that we will point to the different button
// than the one was used when constructing the handler, but not a big deal.
const HistoryMessageReplyMarkup::Button *getButton() const;
// We hold only FullMsgId, not HistoryItem*, because all click handlers
// are activated async and the item may be already destroyed.
void setMessageId(const FullMsgId &msgId) {
_itemId = msgId;
}
protected:
void onClickImpl() const override;
private:
FullMsgId _itemId;
int _row = 0;
int _column = 0;
bool _fullDisplayed = true;
// Returns the full text of the corresponding button.
QString buttonText() const;
};
class ReplyKeyboard {
private:
struct Button;
public:
class Style {
public:
Style(const style::BotKeyboardButton &st) : _st(&st) {
}
virtual void startPaint(Painter &p) const = 0;
virtual const style::TextStyle &textStyle() const = 0;
int buttonSkip() const;
int buttonPadding() const;
int buttonHeight() const;
virtual int buttonRadius() const = 0;
virtual void repaint(not_null<const HistoryItem*> item) const = 0;
virtual ~Style() {
}
protected:
virtual void paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const = 0;
virtual void paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const = 0;
virtual void paintButtonLoading(Painter &p, const QRect &rect) const = 0;
virtual int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const = 0;
private:
const style::BotKeyboardButton *_st;
void paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const;
friend class ReplyKeyboard;
};
ReplyKeyboard(
not_null<const HistoryItem*> item,
std::unique_ptr<Style> &&s);
ReplyKeyboard(const ReplyKeyboard &other) = delete;
ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete;
bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const;
void setStyle(std::unique_ptr<Style> &&s);
void resize(int width, int height);
// what width and height will best fit this keyboard
int naturalWidth() const;
int naturalHeight() const;
void paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const;
ClickHandlerPtr getState(QPoint point) const;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
void clearSelection();
void updateMessageId();
private:
friend class Style;
struct Button {
Button();
Button(Button &&other);
Button &operator=(Button &&other);
~Button();
Text text = { 1 };
QRect rect;
int characters = 0;
float64 howMuchOver = 0.;
HistoryMessageReplyMarkup::Button::Type type;
std::shared_ptr<ReplyMarkupClickHandler> link;
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
};
struct ButtonCoords {
int i, j;
};
void startAnimation(int i, int j, int direction);
ButtonCoords findButtonCoordsByClickHandler(const ClickHandlerPtr &p);
void step_selected(TimeMs ms, bool timer);
const not_null<const HistoryItem*> _item;
int _width = 0;
std::vector<std::vector<Button>> _rows;
base::flat_map<int, TimeMs> _animations;
BasicAnimation _a_selected;
std::unique_ptr<Style> _st;
ClickHandlerPtr _savedPressed;
ClickHandlerPtr _savedActive;
mutable QPoint _savedCoords;
};
// Any HistoryItem can have this Component for
// displaying the day mark above the message.
struct HistoryMessageDate : public RuntimeComponent<HistoryMessageDate> {
void init(const QDateTime &date);
int height() const;
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
};
// Any HistoryItem can have this Component for
// displaying the unread messages bar above the message.
struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar> {
void init(int count);
static int height();
static int marginTop();
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
// If unread bar is freezed the new messages do not
// increment the counter displayed by this bar.
//
// It happens when we've opened the conversation and
// we've seen the bar and new messages are marked as read
// as soon as they are added to the chat history.
bool _freezed = false;
};
struct MessageGroupId {
using Underlying = uint64;
enum Type : Underlying {
None = 0,
} value;
MessageGroupId(Type value = None) : value(value) {
}
static MessageGroupId FromRaw(Underlying value) {
return static_cast<Type>(value);
}
explicit operator bool() const {
return value != None;
}
friend inline Type value_ordering_helper(MessageGroupId value) {
return value.value;
}
};
struct HistoryMessageGroup : public RuntimeComponent<HistoryMessageGroup> {
MessageGroupId groupId = MessageGroupId::None;
HistoryItem *leader = nullptr;
std::vector<not_null<HistoryItem*>> others;
};
class HistoryWebPage;
// Special type of Component for the channel actions log.
struct HistoryMessageLogEntryOriginal : public RuntimeComponent<HistoryMessageLogEntryOriginal> {
HistoryMessageLogEntryOriginal();
HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
~HistoryMessageLogEntryOriginal();
std::unique_ptr<HistoryWebPage> _page;
};
// HistoryMedia has a special owning smart pointer
// which regs/unregs this media to the holding HistoryItem
class HistoryMedia;
class HistoryMediaPtr {
public:
HistoryMediaPtr();
@ -534,7 +176,10 @@ inline TextSelection shiftSelection(TextSelection selection, const Text &byText)
} // namespace internal
class HistoryItem : public HistoryElement, public RuntimeComposer, public ClickHandlerHost {
class HistoryItem
: public HistoryElement
, public RuntimeComposer
, public ClickHandlerHost {
public:
int resizeGetHeight(int newWidth) {
if (_flags & MTPDmessage_ClientFlag::f_pending_init_dimensions) {
@ -564,12 +209,7 @@ public:
const base::flat_map<UserId, bool> &changes) {
}
UserData *viaBot() const {
if (auto via = Get<HistoryMessageVia>()) {
return via->_bot;
}
return nullptr;
}
UserData *viaBot() const;
UserData *getMessageBot() const {
if (auto bot = viaBot()) {
return bot;
@ -584,7 +224,10 @@ public:
bool isLogEntry() const {
return (id > ServerMaxMsgId);
}
void addLogEntryOriginal(WebPageId localId, const QString &label, const TextWithEntities &content);
void addLogEntryOriginal(
WebPageId localId,
const QString &label,
const TextWithEntities &content);
not_null<History*> history() const {
return _history;
@ -642,28 +285,9 @@ public:
return 0;
}
bool definesReplyKeyboard() const {
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return false;
}
return true;
}
bool definesReplyKeyboard() const;
MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const;
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return (_flags & MTPDmessage::Flag::f_reply_markup);
}
MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const {
Expects(definesReplyKeyboard());
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
return markup->flags;
}
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0;
}
bool hasSwitchInlineButton() const {
return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button;
}
@ -864,52 +488,17 @@ public:
virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize
return nullptr;
}
MsgId replyToId() const {
if (auto reply = Get<HistoryMessageReply>()) {
return reply->replyToId();
}
return 0;
}
MsgId replyToId() const;
PeerData *author() const {
return isPost() ? history()->peer : from();
}
QDateTime dateOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalDate;
}
return date;
}
PeerData *senderOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalSender;
}
auto peer = history()->peer;
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
}
PeerData *fromOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (auto user = forwarded->_originalSender->asUser()) {
return user;
}
}
return from();
}
QString authorOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalAuthor;
} else if (auto msgsigned = Get<HistoryMessageSigned>()) {
return msgsigned->_author;
}
return QString();
}
MsgId idOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalId;
}
return id;
}
QDateTime dateOriginal() const;
PeerData *senderOriginal() const;
PeerData *fromOriginal() const;
QString authorOriginal() const;
MsgId idOriginal() const;
// count > 0 - creates the unread bar if necessary and
// sets unread messages count if bar is not freezed yet
@ -939,12 +528,7 @@ public:
setPendingResize();
}
int displayedDateHeight() const {
if (auto date = Get<HistoryMessageDate>()) {
return date->height();
}
return 0;
}
int displayedDateHeight() const;
int marginTop() const;
int marginBottom() const;
bool isAttachedToPrevious() const {
@ -953,27 +537,18 @@ public:
bool isAttachedToNext() const {
return _flags & MTPDmessage_ClientFlag::f_attach_to_next;
}
bool displayDate() const {
return Has<HistoryMessageDate>();
}
bool displayDate() const;
bool isInOneDayWithPrevious() const {
return !isEmpty() && !displayDate();
}
bool isEmpty() const {
return _text.isEmpty() && !_media && !Has<HistoryMessageLogEntryOriginal>();
}
bool isEmpty() const;
bool isHiddenByGroup() const {
return _flags & MTPDmessage_ClientFlag::f_hidden_by_group;
}
MessageGroupId groupId() const {
if (const auto group = Get<HistoryMessageGroup>()) {
return group->groupId;
}
return MessageGroupId::None;
}
MessageGroupId groupId() const;
bool groupIdValidityChanged();
void validateGroupId() {
// Just ignore the result.
@ -1089,20 +664,8 @@ protected:
const ReplyKeyboard *inlineReplyKeyboard() const {
return const_cast<HistoryItem*>(this)->inlineReplyKeyboard();
}
HistoryMessageReplyMarkup *inlineReplyMarkup() {
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return markup;
}
}
return nullptr;
}
ReplyKeyboard *inlineReplyKeyboard() {
if (auto markup = inlineReplyMarkup()) {
return markup->inlineKeyboard.get();
}
return nullptr;
}
HistoryMessageReplyMarkup *inlineReplyMarkup();
ReplyKeyboard *inlineReplyKeyboard();
void invalidateChatsListEntry();
[[nodiscard]] TextSelection skipTextSelection(

View File

@ -0,0 +1,879 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "ui/effects/ripple_animation.h"
#include "history/history_service_layout.h"
#include "history/history_message.h"
#include "history/history_media.h"
#include "history/history_media_types.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<LambdaClickHandler>([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, _textNameOptions);
}
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, _textNameOptions);
}
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()), _textDlgOptions);
updateName();
replyToLnk = goToMessageClickHandler(replyToMsg);
if (!replyToMsg->Has<HistoryMessageForwarded>()) {
if (auto bot = replyToMsg->viaBot()) {
replyToVia = std::make_unique<HistoryMessageVia>();
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, _textNameOptions);
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, ImageRoundCorner::All, 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<HistoryMessageReplyMarkup>()) {
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<const HistoryItem*> item,
std::unique_ptr<Style> &&s)
: _item(item)
, _a_selected(animation(this, &ReplyKeyboard::step_selected))
, _st(std::move(s)) {
if (const auto markup = _item->Get<HistoryMessageReplyMarkup>()) {
const auto context = _item->fullId();
const auto rowCount = int(markup->rows.size());
_rows.reserve(rowCount);
for (auto i = 0; i != rowCount; ++i) {
const auto &row = markup->rows.at(i);
const auto rowSize = int(row.size());
auto newRow = std::vector<Button>();
newRow.reserve(rowSize);
for (auto j = 0; j != rowSize; ++j) {
auto button = Button();
const auto text = row[j].text;
button.type = row.at(j).type;
button.link = std::make_shared<ReplyMarkupClickHandler>(
i,
j,
context);
button.text.setText(
_st->textStyle(),
TextUtilities::SingleLine(text),
_textPlainOptions);
button.characters = text.isEmpty() ? 1 : text.size();
newRow.push_back(std::move(button));
}
_rows.push_back(std::move(newRow));
}
}
}
void ReplyKeyboard::updateMessageId() {
const auto msgId = _item->fullId();
for (const auto &row : _rows) {
for (const auto &button : row) {
button.link->setMessageId(msgId);
}
}
}
void ReplyKeyboard::resize(int width, int height) {
_width = width;
auto markup = _item->Get<HistoryMessageReplyMarkup>();
auto y = 0.;
auto buttonHeight = _rows.empty()
? float64(_st->buttonHeight())
: (float64(height + _st->buttonSkip()) / _rows.size());
for (auto &row : _rows) {
int s = row.size();
int widthForButtons = _width - ((s - 1) * _st->buttonSkip());
int widthForText = widthForButtons;
int widthOfText = 0;
int maxMinButtonWidth = 0;
for_const (auto &button, row) {
widthOfText += qMax(button.text.maxWidth(), 1);
int minButtonWidth = _st->minButtonWidth(button.type);
widthForText -= minButtonWidth;
accumulate_max(maxMinButtonWidth, minButtonWidth);
}
bool exact = (widthForText == widthOfText);
bool enough = (widthForButtons - s * maxMinButtonWidth) >= widthOfText;
float64 x = 0;
for (Button &button : row) {
int buttonw = qMax(button.text.maxWidth(), 1);
float64 textw = buttonw, minw = _st->minButtonWidth(button.type);
float64 w = textw;
if (exact) {
w += minw;
} else if (enough) {
w = (widthForButtons / float64(s));
textw = w - minw;
} else {
textw = (widthForText / float64(s));
w = minw + textw;
accumulate_max(w, 2 * float64(_st->buttonPadding()));
}
int rectx = static_cast<int>(std::floor(x));
int rectw = static_cast<int>(std::floor(x + w)) - rectx;
button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip()));
if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width());
x += w + _st->buttonSkip();
button.link->setFullDisplayed(textw >= buttonw);
}
y += buttonHeight;
}
}
bool ReplyKeyboard::isEnoughSpace(int width, const style::BotKeyboardButton &st) const {
for_const (auto &row, _rows) {
int s = row.size();
int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding);
for_const (auto &button, row) {
widthLeft -= qMax(button.text.maxWidth(), 1);
if (widthLeft < 0) {
if (row.size() > 3) {
return false;
} else {
break;
}
}
}
}
return true;
}
void ReplyKeyboard::setStyle(std::unique_ptr<Style> &&st) {
_st = std::move(st);
}
int ReplyKeyboard::naturalWidth() const {
auto result = 0;
for (const auto &row : _rows) {
auto maxMinButtonWidth = 0;
for (const auto &button : row) {
accumulate_max(
maxMinButtonWidth,
_st->minButtonWidth(button.type));
}
auto rowMaxButtonWidth = 0;
for (const auto &button : row) {
accumulate_max(
rowMaxButtonWidth,
qMax(button.text.maxWidth(), 1) + maxMinButtonWidth);
}
const auto rowSize = int(row.size());
accumulate_max(
result,
rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip());
}
return result;
}
int ReplyKeyboard::naturalHeight() const {
return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight();
}
void ReplyKeyboard::paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const {
Assert(_st != nullptr);
Assert(_width > 0);
_st->startPaint(p);
for_const (auto &row, _rows) {
for_const (auto &button, row) {
QRect rect(button.rect);
if (rect.y() >= clip.y() + clip.height()) return;
if (rect.y() + rect.height() < clip.y()) continue;
// just ignore the buttons that didn't layout well
if (rect.x() + rect.width() > _width) break;
_st->paintButton(p, outerWidth, button, ms);
}
}
}
ClickHandlerPtr ReplyKeyboard::getState(QPoint point) const {
Assert(_width > 0);
for_const (auto &row, _rows) {
for_const (auto &button, row) {
QRect rect(button.rect);
// just ignore the buttons that didn't layout well
if (rect.x() + rect.width() > _width) break;
if (rect.contains(point)) {
_savedCoords = point;
return button.link;
}
}
}
return ClickHandlerPtr();
}
void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (!p) return;
_savedActive = active ? p : ClickHandlerPtr();
auto coords = findButtonCoordsByClickHandler(p);
if (coords.i >= 0 && _savedPressed != p) {
startAnimation(coords.i, coords.j, active ? 1 : -1);
}
}
ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) {
for (int i = 0, rows = _rows.size(); i != rows; ++i) {
auto &row = _rows[i];
for (int j = 0, cols = row.size(); j != cols; ++j) {
if (row[j].link == p) {
return { i, j };
}
}
}
return { -1, -1 };
}
void ReplyKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
if (!p) return;
_savedPressed = pressed ? p : ClickHandlerPtr();
auto coords = findButtonCoordsByClickHandler(p);
if (coords.i >= 0) {
auto &button = _rows[coords.i][coords.j];
if (pressed) {
if (!button.ripple) {
auto mask = Ui::RippleAnimation::roundRectMask(
button.rect.size(),
_st->buttonRadius());
button.ripple = std::make_unique<Ui::RippleAnimation>(
_st->_st->ripple,
std::move(mask),
[this] { _st->repaint(_item); });
}
button.ripple->add(_savedCoords - button.rect.topLeft());
} else {
if (button.ripple) {
button.ripple->lastStop();
}
if (_savedActive != p) {
startAnimation(coords.i, coords.j, -1);
}
}
}
}
void ReplyKeyboard::startAnimation(int i, int j, int direction) {
auto notStarted = _animations.empty();
int indexForAnimation = (i * MatrixRowShift + j + 1) * direction;
_animations.remove(-indexForAnimation);
if (!_animations.contains(indexForAnimation)) {
_animations.emplace(indexForAnimation, getms());
}
if (notStarted && !_a_selected.animating()) {
_a_selected.start();
}
}
void ReplyKeyboard::step_selected(TimeMs ms, bool timer) {
for (auto i = _animations.begin(); i != _animations.end();) {
const auto index = std::abs(i->first) - 1;
const auto row = (index / MatrixRowShift);
const auto col = index % MatrixRowShift;
const auto dt = float64(ms - i->second) / st::botKbDuration;
if (dt >= 1) {
_rows[row][col].howMuchOver = (i->first > 0) ? 1 : 0;
i = _animations.erase(i);
} else {
_rows[row][col].howMuchOver = (i->first > 0) ? dt : (1 - dt);
++i;
}
}
if (timer) _st->repaint(_item);
if (_animations.empty()) {
_a_selected.stop();
}
}
void ReplyKeyboard::clearSelection() {
for (const auto [relativeIndex, time] : _animations) {
const auto index = std::abs(relativeIndex) - 1;
const auto row = (index / MatrixRowShift);
const auto col = index % MatrixRowShift;
_rows[row][col].howMuchOver = 0;
}
_animations.clear();
_a_selected.stop();
}
int ReplyKeyboard::Style::buttonSkip() const {
return _st->margin;
}
int ReplyKeyboard::Style::buttonPadding() const {
return _st->padding;
}
int ReplyKeyboard::Style::buttonHeight() const {
return _st->height;
}
void ReplyKeyboard::Style::paintButton(
Painter &p,
int outerWidth,
const ReplyKeyboard::Button &button,
TimeMs ms) const {
const QRect &rect = button.rect;
paintButtonBg(p, rect, button.howMuchOver);
if (button.ripple) {
button.ripple->paint(p, rect.x(), rect.y(), outerWidth, ms);
if (button.ripple->empty()) {
button.ripple.reset();
}
}
paintButtonIcon(p, rect, outerWidth, button.type);
if (button.type == HistoryMessageMarkupButton::Type::Callback
|| button.type == HistoryMessageMarkupButton::Type::Game) {
if (auto data = button.link->getButton()) {
if (data->requestId) {
paintButtonLoading(p, rect);
}
}
}
int tx = rect.x(), tw = rect.width();
if (tw >= st::botKbStyle.font->elidew + _st->padding * 2) {
tx += _st->padding;
tw -= _st->padding * 2;
} else if (tw > st::botKbStyle.font->elidew) {
tx += (tw - st::botKbStyle.font->elidew) / 2;
tw = st::botKbStyle.font->elidew;
}
button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top);
}
void HistoryMessageReplyMarkup::createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v) {
if (v.isEmpty()) {
rows.clear();
return;
}
rows.reserve(v.size());
for_const (auto &row, v) {
switch (row.type()) {
case mtpc_keyboardButtonRow: {
auto &r = row.c_keyboardButtonRow();
auto &b = r.vbuttons.v;
if (!b.isEmpty()) {
auto buttonRow = std::vector<Button>();
buttonRow.reserve(b.size());
for_const (auto &button, b) {
switch (button.type()) {
case mtpc_keyboardButton: {
buttonRow.push_back({ Button::Type::Default, qs(button.c_keyboardButton().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonCallback: {
auto &buttonData = button.c_keyboardButtonCallback();
buttonRow.push_back({ Button::Type::Callback, qs(buttonData.vtext), qba(buttonData.vdata), 0 });
} break;
case mtpc_keyboardButtonRequestGeoLocation: {
buttonRow.push_back({ Button::Type::RequestLocation, qs(button.c_keyboardButtonRequestGeoLocation().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonRequestPhone: {
buttonRow.push_back({ Button::Type::RequestPhone, qs(button.c_keyboardButtonRequestPhone().vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonUrl: {
auto &buttonData = button.c_keyboardButtonUrl();
buttonRow.push_back({ Button::Type::Url, qs(buttonData.vtext), qba(buttonData.vurl), 0 });
} break;
case mtpc_keyboardButtonSwitchInline: {
auto &buttonData = button.c_keyboardButtonSwitchInline();
auto buttonType = buttonData.is_same_peer() ? Button::Type::SwitchInlineSame : Button::Type::SwitchInline;
buttonRow.push_back({ buttonType, qs(buttonData.vtext), qba(buttonData.vquery), 0 });
if (buttonType == Button::Type::SwitchInline) {
// Optimization flag.
// Fast check on all new messages if there is a switch button to auto-click it.
flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button;
}
} break;
case mtpc_keyboardButtonGame: {
auto &buttonData = button.c_keyboardButtonGame();
buttonRow.push_back({ Button::Type::Game, qs(buttonData.vtext), QByteArray(), 0 });
} break;
case mtpc_keyboardButtonBuy: {
auto &buttonData = button.c_keyboardButtonBuy();
buttonRow.push_back({ Button::Type::Buy, qs(buttonData.vtext), QByteArray(), 0 });
}
}
}
if (!buttonRow.empty()) {
rows.push_back(std::move(buttonRow));
}
}
} break;
}
}
}
void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) {
flags = 0;
rows.clear();
inlineKeyboard = nullptr;
switch (markup.type()) {
case mtpc_replyKeyboardMarkup: {
auto &d = markup.c_replyKeyboardMarkup();
flags = d.vflags.v;
createFromButtonRows(d.vrows.v);
} break;
case mtpc_replyInlineMarkup: {
auto &d = markup.c_replyInlineMarkup();
flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline;
createFromButtonRows(d.vrows.v);
} break;
case mtpc_replyKeyboardHide: {
auto &d = markup.c_replyKeyboardHide();
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero;
} break;
case mtpc_replyKeyboardForceReply: {
auto &d = markup.c_replyKeyboardForceReply();
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply;
} break;
}
}
void HistoryMessageReplyMarkup::create(
const HistoryMessageReplyMarkup &markup) {
flags = markup.flags;
inlineKeyboard = nullptr;
rows.clear();
for (const auto &row : markup.rows) {
auto buttonRow = std::vector<Button>();
buttonRow.reserve(row.size());
for (const auto &button : row) {
buttonRow.push_back({ button.type, button.text, button.data, 0 });
}
if (!buttonRow.empty()) {
rows.push_back(std::move(buttonRow));
}
}
}
void HistoryMessageUnreadBar::init(int count) {
if (_freezed) return;
_text = lng_unread_bar(lt_count, count);
_width = st::semiboldFont->width(_text);
}
int HistoryMessageUnreadBar::height() {
return st::historyUnreadBarHeight + st::historyUnreadBarMargin;
}
int HistoryMessageUnreadBar::marginTop() {
return st::lineWidth + st::historyUnreadBarMargin;
}
void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const {
p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::historyUnreadBarBg);
p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::historyUnreadBarBorder);
p.setFont(st::historyUnreadBarFont);
p.setPen(st::historyUnreadBarFg);
int left = st::msgServiceMargin.left();
int maxwidth = w;
if (Adaptive::ChatWide()) {
maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
w = maxwidth;
p.drawText((w - _width) / 2, y + marginTop() + (st::historyUnreadBarHeight - 2 * st::lineWidth - st::historyUnreadBarFont->height) / 2 + st::historyUnreadBarFont->ascent, _text);
}
void HistoryMessageDate::init(const QDateTime &date) {
_text = langDayOfMonthFull(date.date());
_width = st::msgServiceFont->width(_text);
}
int HistoryMessageDate::height() const {
return st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom() + st::msgServiceMargin.bottom();
}
void HistoryMessageDate::paint(Painter &p, int y, int w) const {
HistoryLayout::ServiceMessagePainter::paintDate(p, _text, _width, y, w);
}
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default;
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other) : _page(std::move(other._page)) {
}
HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=(HistoryMessageLogEntryOriginal &&other) {
_page = std::move(other._page);
return *this;
}
HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
HistoryDocumentCaptioned::HistoryDocumentCaptioned()
: _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) {
}
HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback(const HistoryDocument *that)
: a_progress(0., 0.)
, _a_progress(animation(const_cast<HistoryDocument*>(that), &HistoryDocument::step_voiceProgress)) {
}
void HistoryDocumentVoice::ensurePlayback(const HistoryDocument *that) const {
if (!_playback) {
_playback = std::make_unique<HistoryDocumentVoicePlayback>(that);
}
}
void HistoryDocumentVoice::checkPlaybackFinished() const {
if (_playback && !_playback->_a_progress.animating()) {
_playback.reset();
}
}
void HistoryDocumentVoice::startSeeking() {
_seeking = true;
_seekingCurrent = _seekingStart;
Media::Player::instance()->startSeeking(AudioMsgId::Type::Voice);
}
void HistoryDocumentVoice::stopSeeking() {
_seeking = false;
Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice);
}

View File

@ -0,0 +1,474 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "history/history_item.h"
#include "base/value_ordering.h"
class HistoryDocument;
class HistoryWebPage;
struct MessageGroupId {
using Underlying = uint64;
enum Type : Underlying {
None = 0,
} value;
MessageGroupId(Type value = None) : value(value) {
}
static MessageGroupId FromRaw(Underlying value) {
return static_cast<Type>(value);
}
explicit operator bool() const {
return value != None;
}
friend inline Type value_ordering_helper(MessageGroupId value) {
return value.value;
}
};
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia> {
void create(UserId userId);
void resize(int32 availw) const;
UserData *bot = nullptr;
mutable QString text;
mutable int width = 0;
mutable int maxWidth = 0;
ClickHandlerPtr link;
};
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews> {
QString _viewsText;
int _views = 0;
int _viewsWidth = 0;
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned> {
void refresh(const QString &date);
int maxWidth() const;
QString author;
Text signature;
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited> {
void refresh(const QString &date, bool displayed);
int maxWidth() const;
QDateTime date;
Text text;
};
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded> {
void create(const HistoryMessageVia *via) const;
QDateTime originalDate;
PeerData *originalSender = nullptr;
QString originalAuthor;
MsgId originalId = 0;
mutable Text text = { 1 };
PeerData *savedFromPeer = nullptr;
MsgId savedFromMsgId = 0;
};
struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply> {
HistoryMessageReply() = default;
HistoryMessageReply(const HistoryMessageReply &other) = delete;
HistoryMessageReply(HistoryMessageReply &&other) = delete;
HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete;
HistoryMessageReply &operator=(HistoryMessageReply &&other) {
replyToMsgId = other.replyToMsgId;
std::swap(replyToMsg, other.replyToMsg);
replyToLnk = std::move(other.replyToLnk);
replyToName = std::move(other.replyToName);
replyToText = std::move(other.replyToText);
replyToVersion = other.replyToVersion;
maxReplyWidth = other.maxReplyWidth;
replyToVia = std::move(other.replyToVia);
return *this;
}
~HistoryMessageReply() {
// clearData() should be called by holder.
Expects(replyToMsg == nullptr);
Expects(replyToVia == nullptr);
}
bool updateData(HistoryMessage *holder, bool force = false);
// Must be called before destructor.
void clearData(HistoryMessage *holder);
bool isNameUpdated() const;
void updateName() const;
void resize(int width) const;
void itemRemoved(HistoryMessage *holder, HistoryItem *removed);
enum class PaintFlag {
InBubble = (1 << 0),
Selected = (1 << 1),
};
using PaintFlags = base::flags<PaintFlag>;
friend inline constexpr auto is_flag_type(PaintFlag) { return true; };
void paint(
Painter &p,
const HistoryItem *holder,
int x,
int y,
int w,
PaintFlags flags) const;
MsgId replyToId() const {
return replyToMsgId;
}
int replyToWidth() const {
return maxReplyWidth;
}
ClickHandlerPtr replyToLink() const {
return replyToLnk;
}
MsgId replyToMsgId = 0;
HistoryItem *replyToMsg = nullptr;
ClickHandlerPtr replyToLnk;
mutable Text replyToName, replyToText;
mutable int replyToVersion = 0;
mutable int maxReplyWidth = 0;
std::unique_ptr<HistoryMessageVia> replyToVia;
int toWidth = 0;
};
struct HistoryMessageMarkupButton {
enum class Type {
Default,
Url,
Callback,
RequestPhone,
RequestLocation,
SwitchInline,
SwitchInlineSame,
Game,
Buy,
};
Type type;
QString text;
QByteArray data;
mutable mtpRequestId requestId;
};
struct HistoryMessageReplyMarkup : public RuntimeComponent<HistoryMessageReplyMarkup> {
using Button = HistoryMessageMarkupButton;
HistoryMessageReplyMarkup() = default;
HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) {
}
void create(const MTPReplyMarkup &markup);
void create(const HistoryMessageReplyMarkup &markup);
std::vector<std::vector<Button>> rows;
MTPDreplyKeyboardMarkup::Flags flags = 0;
std::unique_ptr<ReplyKeyboard> inlineKeyboard;
// If >= 0 it holds the y coord of the inlineKeyboard before the last edition.
int oldTop = -1;
private:
void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v);
};
class ReplyMarkupClickHandler : public LeftButtonClickHandler {
public:
ReplyMarkupClickHandler(int row, int column, FullMsgId context);
QString tooltip() const override {
return _fullDisplayed ? QString() : buttonText();
}
void setFullDisplayed(bool full) {
_fullDisplayed = full;
}
// Copy to clipboard support.
void copyToClipboard() const override;
QString copyToClipboardContextItemText() const override;
// Finds the corresponding button in the items markup struct.
// If the button is not found it returns nullptr.
// Note: it is possible that we will point to the different button
// than the one was used when constructing the handler, but not a big deal.
const HistoryMessageMarkupButton *getButton() const;
// We hold only FullMsgId, not HistoryItem*, because all click handlers
// are activated async and the item may be already destroyed.
void setMessageId(const FullMsgId &msgId) {
_itemId = msgId;
}
protected:
void onClickImpl() const override;
private:
FullMsgId _itemId;
int _row = 0;
int _column = 0;
bool _fullDisplayed = true;
// Returns the full text of the corresponding button.
QString buttonText() const;
};
class ReplyKeyboard {
private:
struct Button;
public:
class Style {
public:
Style(const style::BotKeyboardButton &st) : _st(&st) {
}
virtual void startPaint(Painter &p) const = 0;
virtual const style::TextStyle &textStyle() const = 0;
int buttonSkip() const;
int buttonPadding() const;
int buttonHeight() const;
virtual int buttonRadius() const = 0;
virtual void repaint(not_null<const HistoryItem*> item) const = 0;
virtual ~Style() {
}
protected:
virtual void paintButtonBg(
Painter &p,
const QRect &rect,
float64 howMuchOver) const = 0;
virtual void paintButtonIcon(
Painter &p,
const QRect &rect,
int outerWidth,
HistoryMessageMarkupButton::Type type) const = 0;
virtual void paintButtonLoading(
Painter &p,
const QRect &rect) const = 0;
virtual int minButtonWidth(
HistoryMessageMarkupButton::Type type) const = 0;
private:
const style::BotKeyboardButton *_st;
void paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const;
friend class ReplyKeyboard;
};
ReplyKeyboard(
not_null<const HistoryItem*> item,
std::unique_ptr<Style> &&s);
ReplyKeyboard(const ReplyKeyboard &other) = delete;
ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete;
bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const;
void setStyle(std::unique_ptr<Style> &&s);
void resize(int width, int height);
// what width and height will best fit this keyboard
int naturalWidth() const;
int naturalHeight() const;
void paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const;
ClickHandlerPtr getState(QPoint point) const;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
void clearSelection();
void updateMessageId();
private:
friend class Style;
struct Button {
Button();
Button(Button &&other);
Button &operator=(Button &&other);
~Button();
Text text = { 1 };
QRect rect;
int characters = 0;
float64 howMuchOver = 0.;
HistoryMessageMarkupButton::Type type;
std::shared_ptr<ReplyMarkupClickHandler> link;
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
};
struct ButtonCoords {
int i, j;
};
void startAnimation(int i, int j, int direction);
ButtonCoords findButtonCoordsByClickHandler(const ClickHandlerPtr &p);
void step_selected(TimeMs ms, bool timer);
const not_null<const HistoryItem*> _item;
int _width = 0;
std::vector<std::vector<Button>> _rows;
base::flat_map<int, TimeMs> _animations;
BasicAnimation _a_selected;
std::unique_ptr<Style> _st;
ClickHandlerPtr _savedPressed;
ClickHandlerPtr _savedActive;
mutable QPoint _savedCoords;
};
// Any HistoryItem can have this Component for
// displaying the day mark above the message.
struct HistoryMessageDate : public RuntimeComponent<HistoryMessageDate> {
void init(const QDateTime &date);
int height() const;
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
};
// Any HistoryItem can have this Component for
// displaying the unread messages bar above the message.
struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar> {
void init(int count);
static int height();
static int marginTop();
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
// If unread bar is freezed the new messages do not
// increment the counter displayed by this bar.
//
// It happens when we've opened the conversation and
// we've seen the bar and new messages are marked as read
// as soon as they are added to the chat history.
bool _freezed = false;
};
struct HistoryMessageGroup : public RuntimeComponent<HistoryMessageGroup> {
MessageGroupId groupId = MessageGroupId::None;
HistoryItem *leader = nullptr;
std::vector<not_null<HistoryItem*>> others;
};
// Special type of Component for the channel actions log.
struct HistoryMessageLogEntryOriginal : public RuntimeComponent<HistoryMessageLogEntryOriginal> {
HistoryMessageLogEntryOriginal();
HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
~HistoryMessageLogEntryOriginal();
std::unique_ptr<HistoryWebPage> _page;
};
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed> {
ClickHandlerPtr _linksavel, _linkcancell;
int _thumbw = 0;
mutable int _linkw = 0;
mutable QString _link;
};
struct HistoryDocumentCaptioned : public RuntimeComponent<HistoryDocumentCaptioned> {
HistoryDocumentCaptioned();
Text _caption;
};
struct HistoryDocumentNamed : public RuntimeComponent<HistoryDocumentNamed> {
QString _name;
int _namew = 0;
};
struct HistoryDocumentVoicePlayback {
HistoryDocumentVoicePlayback(const HistoryDocument *that);
int32 _position = 0;
anim::value a_progress;
BasicAnimation _a_progress;
};
class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice> {
// We don't use float64 because components should align to pointer even on 32bit systems.
static constexpr float64 kFloatToIntMultiplier = 65536.;
public:
void ensurePlayback(const HistoryDocument *interfaces) const;
void checkPlaybackFinished() const;
mutable std::unique_ptr<HistoryDocumentVoicePlayback> _playback;
std::shared_ptr<VoiceSeekClickHandler> _seekl;
mutable int _lastDurationMs = 0;
bool seeking() const {
return _seeking;
}
void startSeeking();
void stopSeeking();
float64 seekingStart() const {
return _seekingStart / kFloatToIntMultiplier;
}
void setSeekingStart(float64 seekingStart) const {
_seekingStart = qRound(seekingStart * kFloatToIntMultiplier);
}
float64 seekingCurrent() const {
return _seekingCurrent / kFloatToIntMultiplier;
}
void setSeekingCurrent(float64 seekingCurrent) {
_seekingCurrent = qRound(seekingCurrent * kFloatToIntMultiplier);
}
private:
bool _seeking = false;
mutable int _seekingStart = 0;
mutable int _seekingCurrent = 0;
};

View File

@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
struct HistoryMessageEdited;
namespace base {
template <typename Enum>
class enum_mask;

View File

@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "history/history_media_grouped.h"
#include "history/history_item_components.h"
#include "history/history_media_types.h"
#include "history/history_message.h"
#include "storage/storage_shared_media.h"

View File

@ -32,6 +32,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/confirm_box.h"
#include "boxes/add_contact_box.h"
#include "core/click_handler_types.h"
#include "history/history_item_components.h"
#include "history/history_location_manager.h"
#include "history/history_message.h"
#include "window/main_window.h"
@ -1364,39 +1365,6 @@ ImagePtr HistoryVideo::replyPreview() {
return _data->replyPreview;
}
HistoryDocumentCaptioned::HistoryDocumentCaptioned()
: _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) {
}
HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback(const HistoryDocument *that)
: a_progress(0., 0.)
, _a_progress(animation(const_cast<HistoryDocument*>(that), &HistoryDocument::step_voiceProgress)) {
}
void HistoryDocumentVoice::ensurePlayback(const HistoryDocument *that) const {
if (!_playback) {
_playback = std::make_unique<HistoryDocumentVoicePlayback>(that);
}
}
void HistoryDocumentVoice::checkPlaybackFinished() const {
if (_playback && !_playback->_a_progress.animating()) {
_playback.reset();
}
}
void HistoryDocumentVoice::startSeeking() {
_seeking = true;
_seekingCurrent = _seekingStart;
Media::Player::instance()->startSeeking(AudioMsgId::Type::Voice);
}
void HistoryDocumentVoice::stopSeeking() {
_seeking = false;
Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice);
}
HistoryDocument::HistoryDocument(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
@ -1925,6 +1893,26 @@ QString HistoryDocument::inDialogsText() const {
return result;
}
TextSelection HistoryDocument::adjustSelection(
TextSelection selection,
TextSelectType type) const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.adjustSelection(selection, type);
}
return selection;
}
uint16 HistoryDocument::fullSelectionLength() const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.length();
}
return 0;
}
bool HistoryDocument::hasTextForCopy() const {
return Has<HistoryDocumentCaptioned>();
}
TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
TextWithEntities result;
buildStringRepresentation([&result, selection](const QString &type, const QString &fileName, const Text &caption) {
@ -2142,6 +2130,13 @@ bool HistoryDocument::needReSetInlineResultMedia(const MTPMessageMedia &media) {
return needReSetInlineResultDocument(media, _data);
}
TextWithEntities HistoryDocument::getCaption() const {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.originalTextWithEntities();
}
return TextWithEntities();
}
ImagePtr HistoryDocument::replyPreview() {
return _data->makeReplyPreview();
}
@ -2513,7 +2508,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
auto rectw = _width - usew - st::msgReplyPadding.left();
auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
auto forwardedHeightReal = forwarded ? forwarded->_text.countHeight(innerw) : 0;
auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0;
auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height);
if (forwarded) {
recth += forwardedHeight;
@ -2534,11 +2529,11 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
if (forwarded) {
p.setTextPalette(st::serviceTextPalette);
auto breakEverywhere = (forwardedHeightReal > forwardedHeight);
forwarded->_text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere);
forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxGifForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere);
p.restoreTextPalette();
} else if (via) {
p.setFont(st::msgDateFont);
p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->_text);
p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text);
int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
recty += skip;
}
@ -2632,7 +2627,7 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request)
auto rectw = width - usew - st::msgReplyPadding.left();
auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
auto forwardedHeightReal = forwarded ? forwarded->_text.countHeight(innerw) : 0;
auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0;
auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height);
if (forwarded) {
recth += forwardedHeight;
@ -2653,7 +2648,7 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request)
if (breakEverywhere) {
textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
}
result = HistoryTextState(_parent, forwarded->_text.getState(
result = HistoryTextState(_parent, forwarded->text.getState(
point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()),
innerw,
textRequest));
@ -2671,7 +2666,7 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request)
} else if (via) {
auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
if (QRect(rectx, recty, rectw, viah).contains(point)) {
result.link = via->_lnk;
result.link = via->link;
return result;
}
auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
@ -2773,6 +2768,13 @@ Storage::SharedMediaTypesMask HistoryGif::sharedMediaTypes() const {
return Type::File;
}
int HistoryGif::additionalWidth() const {
return additionalWidth(
_parent->Get<HistoryMessageVia>(),
_parent->Get<HistoryMessageReply>(),
_parent->Get<HistoryMessageForwarded>());
}
QString HistoryGif::mediaTypeString() const {
return _data->isVideoMessage()
? lang(lng_in_dlg_video_message)
@ -2874,9 +2876,9 @@ ImagePtr HistoryGif::replyPreview() {
int HistoryGif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const {
int result = 0;
if (forwarded) {
accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->_text.maxWidth() + st::msgReplyPadding.right());
accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right());
} else if (via) {
accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->_maxWidth + st::msgReplyPadding.left());
accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left());
}
if (reply) {
accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
@ -3094,7 +3096,7 @@ void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, T
rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right();
if (via) {
p.setFont(st::msgDateFont);
p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->_text);
p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * rectx + rectw, via->text);
int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
recty += skip;
}
@ -3150,7 +3152,7 @@ HistoryTextState HistorySticker::getState(QPoint point, HistoryStateRequest requ
if (via) {
int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
if (QRect(rectx, recty, rectw, viah).contains(point)) {
result.link = via->_lnk;
result.link = via->link;
return result;
}
int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
@ -3236,7 +3238,7 @@ ImagePtr HistorySticker::replyPreview() {
int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const {
int result = 0;
if (via) {
accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->_maxWidth + st::msgReplyPadding.left());
accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + via->maxWidth + st::msgReplyPadding.left());
}
if (reply) {
accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
@ -3244,6 +3246,12 @@ int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryM
return result;
}
int HistorySticker::additionalWidth() const {
return additionalWidth(
_parent->Get<HistoryMessageVia>(),
_parent->Get<HistoryMessageReply>());
}
namespace {
ClickHandlerPtr sendMessageClickHandler(PeerData *peer) {
@ -4104,6 +4112,10 @@ void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool p
_attach->clickHandlerPressedChanged(p, pressed);
}
}
bool HistoryWebPage::isDisplayed() const {
return !_data->pendingTill
&& !_parent->Has<HistoryMessageLogEntryOriginal>();
}
void HistoryWebPage::attachToParent() {
App::regWebPageItem(_data, _parent);

View File

@ -27,6 +27,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "data/data_web_page.h"
#include "data/data_game.h"
class ReplyMarkupClickHandler;
struct HistoryDocumentNamed;
struct HistoryMessageVia;
struct HistoryMessageReply;
struct HistoryMessageForwarded;
namespace Media {
namespace Clip {
class Playback;
@ -402,68 +408,6 @@ private:
};
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed> {
ClickHandlerPtr _linksavel, _linkcancell;
int _thumbw = 0;
mutable int _linkw = 0;
mutable QString _link;
};
struct HistoryDocumentCaptioned : public RuntimeComponent<HistoryDocumentCaptioned> {
HistoryDocumentCaptioned();
Text _caption;
};
struct HistoryDocumentNamed : public RuntimeComponent<HistoryDocumentNamed> {
QString _name;
int _namew = 0;
};
class HistoryDocument;
struct HistoryDocumentVoicePlayback {
HistoryDocumentVoicePlayback(const HistoryDocument *that);
int32 _position = 0;
anim::value a_progress;
BasicAnimation _a_progress;
};
class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice> {
// We don't use float64 because components should align to pointer even on 32bit systems.
static constexpr float64 kFloatToIntMultiplier = 65536.;
public:
void ensurePlayback(const HistoryDocument *interfaces) const;
void checkPlaybackFinished() const;
mutable std::unique_ptr<HistoryDocumentVoicePlayback> _playback;
std::shared_ptr<VoiceSeekClickHandler> _seekl;
mutable int _lastDurationMs = 0;
bool seeking() const {
return _seeking;
}
void startSeeking();
void stopSeeking();
float64 seekingStart() const {
return _seekingStart / kFloatToIntMultiplier;
}
void setSeekingStart(float64 seekingStart) const {
_seekingStart = qRound(seekingStart * kFloatToIntMultiplier);
}
float64 seekingCurrent() const {
return _seekingCurrent / kFloatToIntMultiplier;
}
void setSeekingCurrent(float64 seekingCurrent) {
_seekingCurrent = qRound(seekingCurrent * kFloatToIntMultiplier);
}
private:
bool _seeking = false;
mutable int _seekingStart = 0;
mutable int _seekingCurrent = 0;
};
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
public:
HistoryDocument(
@ -497,22 +441,10 @@ public:
void updatePressed(QPoint point) override;
[[nodiscard]] TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const override {
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.adjustSelection(selection, type);
}
return selection;
}
uint16 fullSelectionLength() const override {
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.length();
}
return 0;
}
bool hasTextForCopy() const override {
return Has<HistoryDocumentCaptioned>();
}
TextSelection selection,
TextSelectType type) const override;
uint16 fullSelectionLength() const override;
bool hasTextForCopy() const override;
QString notificationText() const override;
QString inDialogsText() const override;
@ -541,12 +473,7 @@ public:
}
ImagePtr replyPreview() override;
TextWithEntities getCaption() const override {
if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.originalTextWithEntities();
}
return TextWithEntities();
}
TextWithEntities getCaption() const override;
bool needsBubble() const override {
return true;
}
@ -693,10 +620,11 @@ protected:
}
private:
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const;
int additionalWidth() const {
return additionalWidth(_parent->Get<HistoryMessageVia>(), _parent->Get<HistoryMessageReply>(), _parent->Get<HistoryMessageForwarded>());
}
int additionalWidth(
const HistoryMessageVia *via,
const HistoryMessageReply *reply,
const HistoryMessageForwarded *forwarded) const;
int additionalWidth() const;
QString mediaTypeString() const;
bool isSeparateRoundVideo() const;
@ -777,9 +705,7 @@ public:
private:
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
int additionalWidth() const {
return additionalWidth(_parent->Get<HistoryMessageVia>(), _parent->Get<HistoryMessageReply>());
}
int additionalWidth() const;
QString toString() const;
int16 _pixw = 1;
@ -978,9 +904,7 @@ public:
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
bool isDisplayed() const override {
return !_data->pendingTill && !_parent->Has<HistoryMessageLogEntryOriginal>();
}
bool isDisplayed() const override;
PhotoData *getPhoto() const override {
return _attach ? _attach->getPhoto() : nullptr;
}

View File

@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "mainwindow.h"
#include "apiwrap.h"
#include "history/history_item_components.h"
#include "history/history_location_manager.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
@ -46,6 +47,97 @@ namespace {
constexpr auto kPinnedMessageTextLimit = 16;
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;
}
inline void initTextOptions() {
_historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir();
_textDlgOptions.maxw = st::columnMaximalWidthLeft * 2;
@ -321,7 +413,8 @@ void HistoryInitMessages() {
initTextOptions();
}
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(const FullMsgId &msgId) {
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
const FullMsgId &msgId) {
return [dependent = msgId](ChannelData *channel, MsgId msgId) {
if (auto item = App::histItemById(dependent)) {
item->updateDependencyItem();
@ -363,293 +456,26 @@ QString GetErrorTextForForward(
return QString();
}
void HistoryMessageVia::create(UserId userId) {
_bot = App::user(peerFromUser(userId));
_maxWidth = st::msgServiceNameFont->width(lng_inline_bot_via(lt_inline_bot, '@' + _bot->username));
_lnk = std::make_shared<LambdaClickHandler>([bot = _bot] {
App::insertBotCommand('@' + bot->username);
});
}
struct HistoryMessage::CreateConfig {
MsgId replyTo = 0;
UserId viaBotId = 0;
int viewsCount = -1;
QString author;
PeerId senderOriginal = 0;
MsgId originalId = 0;
PeerId savedFromPeer = 0;
MsgId savedFromMsgId = 0;
QString authorOriginal;
QDateTime originalDate;
QDateTime editDate;
MessageGroupId groupId = MessageGroupId::None;
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;
}
}
}
// For messages created from MTP structs.
const MTPReplyMarkup *mtpMarkup = nullptr;
void HistoryMessageSigned::create(const QString &author, 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);
}
_author = author;
_signature.setText(st::msgDateTextStyle, name + time, _textNameOptions);
}
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, _textNameOptions);
}
int HistoryMessageEdited::maxWidth() const {
return text.maxWidth();
}
void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
QString text;
auto fromChannel = (_originalSender->isChannel() && !_originalSender->isMegagroup());
if (!_originalAuthor.isEmpty()) {
text = lng_forwarded_signed(lt_channel, App::peerName(_originalSender), lt_user, _originalAuthor);
} else {
text = App::peerName(_originalSender);
}
if (via) {
if (fromChannel) {
text = lng_forwarded_channel_via(lt_channel, textcmdLink(1, text), lt_inline_bot, textcmdLink(2, '@' + via->_bot->username));
} else {
text = lng_forwarded_via(lt_user, textcmdLink(1, text), lt_inline_bot, textcmdLink(2, '@' + via->_bot->username));
}
} else {
if (fromChannel) {
text = lng_forwarded_channel(lt_channel, textcmdLink(1, text));
} else {
text = lng_forwarded(lt_user, textcmdLink(1, text));
}
}
TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto };
_text.setText(st::fwdTextStyle, text, opts);
_text.setLink(1, fromChannel ? goToMessageClickHandler(_originalSender, _originalId) : _originalSender->openLink());
if (via) {
_text.setLink(2, via->_lnk);
}
}
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()), _textDlgOptions);
updateName();
replyToLnk = goToMessageClickHandler(replyToMsg);
if (!replyToMsg->Has<HistoryMessageForwarded>()) {
if (auto bot = replyToMsg->viaBot()) {
_replyToVia = std::make_unique<HistoryMessageVia>();
_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, _textNameOptions);
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, ImageRoundCorner::All, 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));
}
}
}
void HistoryMessage::KeyboardStyle::startPaint(Painter &p) const {
p.setPen(st::msgServiceFg);
}
const style::TextStyle &HistoryMessage::KeyboardStyle::textStyle() const {
return st::serviceTextStyle;
}
void HistoryMessage::KeyboardStyle::repaint(not_null<const HistoryItem*> item) const {
Auth().data().requestItemRepaint(item);
}
int HistoryMessage::KeyboardStyle::buttonRadius() const {
return st::dateRadius;
}
void HistoryMessage::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 HistoryMessage::KeyboardStyle::paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const {
using Button = HistoryMessageReplyMarkup::Button;
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 HistoryMessage::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 HistoryMessage::KeyboardStyle::minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const {
using Button = HistoryMessageReplyMarkup::Button;
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;
}
// For messages created from existing messages (forwarded).
const HistoryMessageReplyMarkup *inlineMarkup = nullptr;
};
HistoryMessage::HistoryMessage(
not_null<History*> history,
@ -922,12 +748,26 @@ void HistoryMessage::updateMediaInBubbleState() {
_media->setInBubbleState(computeState());
}
int HistoryMessage::viewsCount() const {
if (const auto views = Get<HistoryMessageViews>()) {
return views->_views;
}
return HistoryItem::viewsCount();
}
not_null<PeerData*> HistoryMessage::displayFrom() const {
return history()->peer->isSelf()
? senderOriginal()
: author();
}
bool HistoryMessage::updateDependencyItem() {
if (const auto reply = Get<HistoryMessageReply>()) {
return reply->updateData(this, true);
}
return true;
}
void HistoryMessage::updateAdminBadgeState() {
auto hasAdminBadge = [&] {
if (auto channel = history()->peer->asChannel()) {
@ -1018,7 +858,7 @@ bool HistoryMessage::displayFastShare() const {
bool HistoryMessage::displayGoToOriginal() const {
if (_history->peer->isSelf()) {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_savedFromPeer && forwarded->_savedFromMsgId;
return forwarded->savedFromPeer && forwarded->savedFromMsgId;
}
}
return false;
@ -1084,13 +924,16 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
if (const auto edited = Get<HistoryMessageEdited>()) {
edited->date = config.editDate;
}
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->author = config.author;
}
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
forwarded->_originalDate = config.originalDate;
forwarded->_originalSender = App::peer(config.senderOriginal);
forwarded->_originalId = config.originalId;
forwarded->_originalAuthor = config.authorOriginal;
forwarded->_savedFromPeer = App::peerLoaded(config.savedFromPeer);
forwarded->_savedFromMsgId = config.savedFromMsgId;
forwarded->originalDate = config.originalDate;
forwarded->originalSender = App::peer(config.senderOriginal);
forwarded->originalId = config.originalId;
forwarded->originalAuthor = config.authorOriginal;
forwarded->savedFromPeer = App::peerLoaded(config.savedFromPeer);
forwarded->savedFromMsgId = config.savedFromMsgId;
}
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
if (config.mtpMarkup) {
@ -1224,7 +1067,7 @@ void HistoryMessage::replaceBuyWithReceiptInMarkup() {
if (auto markup = inlineReplyMarkup()) {
for (auto &row : markup->rows) {
for (auto &button : row) {
if (button.type == HistoryMessageReplyMarkup::Button::Type::Buy) {
if (button.type == HistoryMessageMarkupButton::Type::Buy) {
button.text = lang(lng_payments_receipt_button);
}
}
@ -1314,7 +1157,7 @@ void HistoryMessage::initDimensions() {
+ displayFrom()->nameText.maxWidth()
+ st::msgPadding.right();
if (via && !forwarded) {
namew += st::msgServiceFont->spacew + via->_maxWidth;
namew += st::msgServiceFont->spacew + via->maxWidth;
}
if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
auto badgeWidth = st::msgServiceFont->width(
@ -1323,19 +1166,19 @@ void HistoryMessage::initDimensions() {
}
accumulate_max(_maxw, namew);
} else if (via && !forwarded) {
accumulate_max(_maxw, st::msgPadding.left() + via->_maxWidth + st::msgPadding.right());
accumulate_max(_maxw, st::msgPadding.left() + via->maxWidth + st::msgPadding.right());
}
if (forwarded) {
auto namew = st::msgPadding.left() + forwarded->_text.maxWidth() + st::msgPadding.right();
auto namew = st::msgPadding.left() + forwarded->text.maxWidth() + st::msgPadding.right();
if (via) {
namew += st::msgServiceFont->spacew + via->_maxWidth;
namew += st::msgServiceFont->spacew + via->maxWidth;
}
accumulate_max(_maxw, namew);
}
if (reply) {
auto replyw = st::msgPadding.left() + reply->_maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right();
if (reply->_replyToVia) {
replyw += st::msgServiceFont->spacew + reply->_replyToVia->_maxWidth;
auto replyw = st::msgPadding.left() + reply->maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right();
if (reply->replyToVia) {
replyw += st::msgServiceFont->spacew + reply->replyToVia->maxWidth;
}
accumulate_max(_maxw, replyw);
}
@ -1486,11 +1329,11 @@ void HistoryMessage::refreshEditedBadge() {
if (edited) {
edited->refresh(dateText, !editDate.isNull());
}
if (auto msgsigned = Get<HistoryMessageSigned>()) {
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
const auto text = (!edited || editDate.isNull())
? dateText
: edited->text.originalText();
msgsigned->create(msgsigned->_author, text);
msgsigned->refresh(text);
}
initTime();
}
@ -1504,7 +1347,7 @@ bool HistoryMessage::displayForwardedFrom() const {
|| !_media
|| !_media->isDisplayed()
|| !_media->hideForwardedFrom()
|| forwarded->_originalSender->isChannel();
|| forwarded->originalSender->isChannel();
}
return false;
}
@ -1604,7 +1447,7 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
}
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (selection == FullSelection) {
auto fwdinfo = forwarded->_text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
auto fwdinfo = forwarded->text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
auto wrapped = TextWithEntities();
wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.size());
@ -1795,7 +1638,7 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width
dateX += HistoryMessage::timeLeft();
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->_signature.drawElided(p, dateX, dateY, _timeWidth);
msgsigned->signature.drawElided(p, dateX, dateY, _timeWidth);
} else if (const auto edited = displayedEditBadge()) {
edited->text.drawElided(p, dateX, dateY, _timeWidth);
} else {
@ -2062,8 +1905,8 @@ void HistoryMessage::paintFromName(Painter &p, QRect &trect, bool selected) cons
if (via && !forwarded && availableWidth > 0) {
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;
p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->text);
auto skipWidth = via->width + st::msgServiceFont->spacew;
availableLeft += skipWidth;
availableWidth -= skipWidth;
}
@ -2088,12 +1931,12 @@ void HistoryMessage::paintForwardedInfo(Painter &p, QRect &trect, bool selected)
p.setFont(serviceFont);
auto forwarded = Get<HistoryMessageForwarded>();
auto breakEverywhere = (forwarded->_text.countHeight(trect.width()) > 2 * serviceFont->height);
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);
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));
trect.setY(trect.y() + (((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * serviceFont->height));
}
}
@ -2116,7 +1959,7 @@ void HistoryMessage::paintViaBotIdInfo(Painter &p, QRect &trect, bool selected)
if (auto via = Get<HistoryMessageVia>()) {
p.setFont(st::msgServiceNameFont);
p.setPen(selected ? (hasOutLayout() ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (hasOutLayout() ? st::msgOutServiceFg : st::msgInServiceFg));
p.drawTextLeft(trect.left(), trect.top(), width(), via->_text);
p.drawTextLeft(trect.left(), trect.top(), width(), via->text);
trect.setY(trect.y() + st::msgServiceNameFont->height);
}
}
@ -2236,7 +2079,7 @@ int HistoryMessage::performResizeGetHeight() {
}
if (displayForwardedFrom()) {
auto fwdheight = ((forwarded->_text.maxWidth() > (countGeometry().width() - st::msgPadding.left() - st::msgPadding.right())) ? 2 : 1) * st::semiboldFont->height;
auto fwdheight = ((forwarded->text.maxWidth() > (countGeometry().width() - st::msgPadding.left() - st::msgPadding.right())) ? 2 : 1) * st::semiboldFont->height;
_height += fwdheight;
}
@ -2403,8 +2246,8 @@ ClickHandlerPtr HistoryMessage::rightActionLink() const {
if (!_rightActionLink) {
const auto itemId = fullId();
const auto forwarded = Get<HistoryMessageForwarded>();
const auto savedFromPeer = forwarded ? forwarded->_savedFromPeer : nullptr;
const auto savedFromMsgId = forwarded ? forwarded->_savedFromMsgId : 0;
const auto savedFromPeer = forwarded ? forwarded->savedFromPeer : nullptr;
const auto savedFromMsgId = forwarded ? forwarded->savedFromMsgId : 0;
_rightActionLink = std::make_shared<LambdaClickHandler>([=] {
if (auto item = App::histItemById(itemId)) {
if (savedFromPeer && savedFromMsgId) {
@ -2442,7 +2285,7 @@ void HistoryMessage::updatePressed(QPoint point) {
if (displayFromName()) trect.setTop(trect.top() + st::msgNameFont->height);
if (displayForwardedFrom()) {
auto forwarded = Get<HistoryMessageForwarded>();
auto fwdheight = ((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
trect.setTop(trect.top() + fwdheight);
}
if (Get<HistoryMessageReply>()) {
@ -2485,8 +2328,8 @@ bool HistoryMessage::getStateFromName(
}
auto forwarded = Get<HistoryMessageForwarded>();
auto via = Get<HistoryMessageVia>();
if (via && !forwarded && point.x() >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && point.x() < trect.left() + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->_width) {
outResult->link = via->_lnk;
if (via && !forwarded && point.x() >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && point.x() < trect.left() + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->width) {
outResult->link = via->link;
return true;
}
}
@ -2502,14 +2345,14 @@ bool HistoryMessage::getStateForwardedInfo(
const HistoryStateRequest &request) const {
if (displayForwardedFrom()) {
auto forwarded = Get<HistoryMessageForwarded>();
auto fwdheight = ((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
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 breakEverywhere = (forwarded->text.countHeight(trect.width()) > 2 * st::semiboldFont->height);
auto textRequest = request.forText();
if (breakEverywhere) {
textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
}
*outResult = HistoryTextState(this, forwarded->_text.getState(
*outResult = HistoryTextState(this, forwarded->text.getState(
point - trect.topLeft(),
trect.width(),
textRequest));
@ -2550,8 +2393,8 @@ bool HistoryMessage::getStateViaBotIdInfo(
not_null<HistoryTextState*> outResult) const {
if (!displayFromName() && !Has<HistoryMessageForwarded>()) {
if (auto via = Get<HistoryMessageVia>()) {
if (QRect(trect.x(), trect.y(), via->_width, st::msgNameFont->height).contains(point)) {
outResult->link = via->_lnk;
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);

View File

@ -20,15 +20,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "history/history_item.h"
struct HistoryMessageEdited;
void HistoryInitMessages();
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(const FullMsgId &msgId);
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
const FullMsgId &msgId);
MTPDmessage::Flags NewMessageFlags(not_null<PeerData*> peer);
QString GetErrorTextForForward(
not_null<PeerData*> peer,
const HistoryItemsList &items);
void FastShareMessage(not_null<HistoryItem*> item);
class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> {
class HistoryMessage
: public HistoryItem
, private HistoryItemInstantiated<HistoryMessage> {
public:
static not_null<HistoryMessage*> create(
not_null<History*> history,
@ -218,21 +225,9 @@ public:
return _timeWidth;
}
int viewsCount() const override {
if (auto views = Get<HistoryMessageViews>()) {
return views->_views;
}
return HistoryItem::viewsCount();
}
int viewsCount() const override;
not_null<PeerData*> displayFrom() const;
bool updateDependencyItem() override {
if (auto reply = Get<HistoryMessageReply>()) {
return reply->updateData(this, true);
}
return true;
}
bool updateDependencyItem() override;
MsgId dependencyMsgId() const override {
return replyToId();
}
@ -372,47 +367,10 @@ private:
mutable ClickHandlerPtr _rightActionLink;
mutable int32 _fromNameVersion = 0;
struct CreateConfig {
MsgId replyTo = 0;
UserId viaBotId = 0;
int viewsCount = -1;
QString author;
PeerId senderOriginal = 0;
MsgId originalId = 0;
PeerId savedFromPeer = 0;
MsgId savedFromMsgId = 0;
QString authorOriginal;
QDateTime originalDate;
QDateTime editDate;
MessageGroupId groupId = MessageGroupId::None;
// For messages created from MTP structs.
const MTPReplyMarkup *mtpMarkup = nullptr;
// For messages created from existing messages (forwarded).
const HistoryMessageReplyMarkup *inlineMarkup = nullptr;
};
struct CreateConfig;
void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup);
void createComponents(const CreateConfig &config);
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, HistoryMessageReplyMarkup::Button::Type type) const override;
void paintButtonLoading(Painter &p, const QRect &rect) const override;
int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const override;
};
void updateMediaInBubbleState();
void updateAdminBadgeState();

View File

@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_message.h"
#include "history/history_item_components.h"
#include "auth_session.h"
#include "window/notifications_manager.h"
#include "storage/storage_shared_media.h"

View File

@ -46,6 +46,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_media_types.h"
#include "history/history_drag_area.h"
#include "history/history_inner_widget.h"
#include "history/history_item_components.h"
#include "profile/profile_block_group_members.h"
#include "info/info_memento.h"
#include "core/click_handler_types.h"
@ -3320,7 +3321,11 @@ void HistoryWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
}
}
void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, not_null<const HistoryItem*> msg, int row, int col) {
void HistoryWidget::app_sendBotCallback(
not_null<const HistoryMessageMarkupButton*> button,
not_null<const HistoryItem*> msg,
int row,
int column) {
if (msg->id < 0 || _peer != msg->history()->peer) {
return;
}
@ -3329,8 +3334,14 @@ void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button
auto bot = msg->getMessageBot();
using ButtonType = HistoryMessageReplyMarkup::Button::Type;
BotCallbackInfo info = { bot, msg->fullId(), row, col, (button->type == ButtonType::Game) };
using ButtonType = HistoryMessageMarkupButton::Type;
BotCallbackInfo info = {
bot,
msg->fullId(),
row,
column,
(button->type == ButtonType::Game)
};
auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0);
QByteArray sendData;
if (info.game) {

View File

@ -74,6 +74,7 @@ class BotKeyboard;
class MessageField;
class HistoryInner;
class HistoryTopBarWidget;
struct HistoryMessageMarkupButton;
class ReportSpamPanel : public TWidget {
Q_OBJECT
@ -332,7 +333,11 @@ public:
bool wheelEventFromFloatPlayer(QEvent *e) override;
QRect rectForFloatPlayer() const override;
void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, not_null<const HistoryItem*> msg, int row, int col);
void app_sendBotCallback(
not_null<const HistoryMessageMarkupButton*> button,
not_null<const HistoryItem*> msg,
int row,
int column);
PeerData *ui_getPeerForMouseAction();

View File

@ -1612,8 +1612,12 @@ void MainWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
_history->hideSingleUseKeyboard(peer, replyTo);
}
void MainWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col) {
_history->app_sendBotCallback(button, msg, row, col);
void MainWidget::app_sendBotCallback(
not_null<const HistoryMessageMarkupButton*> button,
not_null<const HistoryItem*> msg,
int row,
int column) {
_history->app_sendBotCallback(button, msg, row, column);
}
bool MainWidget::insertBotCommand(const QString &cmd) {

View File

@ -25,6 +25,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/weak_ptr.h"
#include "ui/rp_widget.h"
struct HistoryMessageMarkupButton;
class MainWindow;
class ConfirmBox;
class DialogsWidget;
class HistoryWidget;
class HistoryHider;
class StackItem;
namespace Notify {
struct PeerUpdate;
} // namespace Notify
@ -66,14 +74,6 @@ class Call;
class TopBar;
} // namespace Calls
class MainWindow;
class ConfirmBox;
class DialogsWidget;
class HistoryWidget;
class HistoryHider;
class StackItem;
namespace InlineBots {
namespace Layout {
class ItemBase;
@ -336,7 +336,11 @@ public:
void documentLoadProgress(DocumentData *document);
void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col);
void app_sendBotCallback(
not_null<const HistoryMessageMarkupButton*> button,
not_null<const HistoryItem*> msg,
int row,
int column);
void ui_showPeerHistory(
PeerId peer,

View File

@ -35,6 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "media/player/media_player_instance.h"
#include "storage/localstorage.h"
#include "history/history_media_types.h"
#include "history/history_item_components.h"
#include "ui/effects/round_checkbox.h"
namespace Overview {
@ -751,7 +752,7 @@ const style::RoundCheckbox &Voice::checkboxStyle() const {
void Voice::updateName() {
auto version = 0;
if (auto forwarded = parent()->Get<HistoryMessageForwarded>()) {
if (const auto forwarded = parent()->Get<HistoryMessageForwarded>()) {
if (parent()->fromOriginal()->isChannel()) {
_name.setText(st::semiboldTextStyle, lng_forwarded_channel(lt_channel, App::peerName(parent()->fromOriginal())), _textNameOptions);
} else {

View File

@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "window/notifications_manager_default.h"
#include "media/media_audio_track.h"
#include "media/media_audio.h"
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "mainwidget.h"

View File

@ -215,6 +215,8 @@
<(src_loc)/history/history_drag_area.h
<(src_loc)/history/history_item.cpp
<(src_loc)/history/history_item.h
<(src_loc)/history/history_item_components.cpp
<(src_loc)/history/history_item_components.h
<(src_loc)/history/history_inner_widget.cpp
<(src_loc)/history/history_inner_widget.h
<(src_loc)/history/history_location_manager.cpp