mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 10:11:41 -05:00
Move message components to history_item_components.
Also fix channel signatures rendering.
This commit is contained in:
parent
16ca2d39c5
commit
92333e982c
28 changed files with 1941 additions and 1638 deletions
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
879
Telegram/SourceFiles/history/history_item_components.cpp
Normal file
879
Telegram/SourceFiles/history/history_item_components.cpp
Normal 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);
|
||||
}
|
474
Telegram/SourceFiles/history/history_item_components.h
Normal file
474
Telegram/SourceFiles/history/history_item_components.h
Normal 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;
|
||||
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue