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 "mainwidget.h"
|
||||||
#include "boxes/add_contact_box.h"
|
#include "boxes/add_contact_box.h"
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "auth_session.h"
|
#include "auth_session.h"
|
||||||
#include "boxes/confirm_box.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_service_layout.h"
|
||||||
#include "history/history_location_manager.h"
|
#include "history/history_location_manager.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "media/media_audio.h"
|
#include "media/media_audio.h"
|
||||||
#include "inline_bots/inline_bot_layout_item.h"
|
#include "inline_bots/inline_bot_layout_item.h"
|
||||||
#include "messenger.h"
|
#include "messenger.h"
|
||||||
|
|
|
@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
#include "storage/serialize_common.h"
|
#include "storage/serialize_common.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "window/notifications_manager.h"
|
#include "window/notifications_manager.h"
|
||||||
#include "platform/platform_specific.h"
|
#include "platform/platform_specific.h"
|
||||||
#include "calls/calls_instance.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 "chat_helpers/bot_keyboard.h"
|
||||||
|
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
#include "styles/style_history.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)
|
BotKeyboard::BotKeyboard(QWidget *parent) : TWidget(parent)
|
||||||
, _st(&st::botKbButton) {
|
, _st(&st::botKbButton) {
|
||||||
setGeometry(0, 0, _st->margin, st::botKbScroll.deltat);
|
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) {
|
void BotKeyboard::mousePressEvent(QMouseEvent *e) {
|
||||||
_lastMousePos = e->globalPos();
|
_lastMousePos = e->globalPos();
|
||||||
updateSelected();
|
updateSelected();
|
||||||
|
@ -250,3 +298,5 @@ void BotKeyboard::updateSelected() {
|
||||||
setCursor(link ? style::cur_pointer : style::cur_default);
|
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"
|
#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
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -57,6 +62,8 @@ public:
|
||||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||||
|
|
||||||
|
~BotKeyboard();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int resizeGetHeight(int newWidth) override;
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
|
||||||
|
@ -83,27 +90,6 @@ private:
|
||||||
QPoint _lastMousePos;
|
QPoint _lastMousePos;
|
||||||
std::unique_ptr<ReplyKeyboard> _impl;
|
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;
|
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 "core/click_handler_types.h"
|
||||||
#include "media/media_clip_reader.h"
|
#include "media/media_clip_reader.h"
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "observer_peer.h"
|
#include "observer_peer.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
@ -69,19 +70,22 @@ bool insertBotCommand(const QString &cmd) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void activateBotCommand(const HistoryItem *msg, int row, int col) {
|
void activateBotCommand(
|
||||||
const HistoryMessageReplyMarkup::Button *button = nullptr;
|
not_null<const HistoryItem*> msg,
|
||||||
|
int row,
|
||||||
|
int column) {
|
||||||
|
const HistoryMessageMarkupButton *button = nullptr;
|
||||||
if (auto markup = msg->Get<HistoryMessageReplyMarkup>()) {
|
if (auto markup = msg->Get<HistoryMessageReplyMarkup>()) {
|
||||||
if (row < markup->rows.size()) {
|
if (row < markup->rows.size()) {
|
||||||
auto &buttonRow = markup->rows[row];
|
auto &buttonRow = markup->rows[row];
|
||||||
if (col < buttonRow.size()) {
|
if (column < buttonRow.size()) {
|
||||||
button = &buttonRow.at(col);
|
button = &buttonRow[column];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!button) return;
|
if (!button) return;
|
||||||
|
|
||||||
using ButtonType = HistoryMessageReplyMarkup::Button::Type;
|
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||||
switch (button->type) {
|
switch (button->type) {
|
||||||
case ButtonType::Default: {
|
case ButtonType::Default: {
|
||||||
// Copy string before passing it to the sending method
|
// 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::Callback:
|
||||||
case ButtonType::Game: {
|
case ButtonType::Game: {
|
||||||
if (auto m = main()) {
|
if (auto m = main()) {
|
||||||
m->app_sendBotCallback(button, msg, row, col);
|
m->app_sendBotCallback(button, msg, row, column);
|
||||||
}
|
}
|
||||||
} break;
|
} 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);
|
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 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 joinGroupByHash(const QString &hash);
|
||||||
void removeDialog(History *history);
|
void removeDialog(History *history);
|
||||||
void showSettings();
|
void showSettings();
|
||||||
|
|
|
@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
#include "history/history_service.h"
|
#include "history/history_service.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "dialogs/dialogs_indexed_list.h"
|
#include "dialogs/dialogs_indexed_list.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
#include "data/data_drafts.h"
|
#include "data/data_drafts.h"
|
||||||
|
@ -679,7 +680,7 @@ void checkForSwitchInlineButton(HistoryItem *item) {
|
||||||
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||||
for_const (auto &row, markup->rows) {
|
for_const (auto &row, markup->rows) {
|
||||||
for_const (auto &button, row) {
|
for_const (auto &button, row) {
|
||||||
if (button.type == HistoryMessageReplyMarkup::Button::Type::SwitchInline) {
|
if (button.type == HistoryMessageMarkupButton::Type::SwitchInline) {
|
||||||
Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data));
|
Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "history/history_service_layout.h"
|
#include "history/history_service_layout.h"
|
||||||
#include "history/history_admin_log_section.h"
|
#include "history/history_admin_log_section.h"
|
||||||
#include "history/history_admin_log_filter.h"
|
#include "history/history_admin_log_filter.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "chat_helpers/message_field.h"
|
#include "chat_helpers/message_field.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
@ -424,17 +425,17 @@ void InnerWidget::updateEmptyText() {
|
||||||
|
|
||||||
QString InnerWidget::tooltipText() const {
|
QString InnerWidget::tooltipText() const {
|
||||||
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
|
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));
|
auto dateText = item->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||||
return dateText;
|
return dateText;
|
||||||
}
|
}
|
||||||
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
|
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
|
||||||
if (auto item = App::hoveredItem()) {
|
if (const auto item = App::hoveredItem()) {
|
||||||
if (auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||||
return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
|
return forwarded->text.originalText(AllTextSelection, ExpandLinksNone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (auto lnk = ClickHandler::getActive()) {
|
} else if (const auto lnk = ClickHandler::getActive()) {
|
||||||
return lnk->tooltip();
|
return lnk->tooltip();
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
|
|
|
@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
#include "history/history_service_layout.h"
|
#include "history/history_service_layout.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
#include "window/window_peer_menu.h"
|
#include "window/window_peer_menu.h"
|
||||||
|
@ -2774,21 +2775,22 @@ void HistoryInner::applyDragSelection(
|
||||||
|
|
||||||
QString HistoryInner::tooltipText() const {
|
QString HistoryInner::tooltipText() const {
|
||||||
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
|
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
|
||||||
if (App::hoveredItem()) {
|
if (const auto item = App::hoveredItem()) {
|
||||||
auto dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
auto dateText = item->date.toString(
|
||||||
auto editedDate = App::hoveredItem()->displayedEditDate();
|
QLocale::system().dateTimeFormat(QLocale::LongFormat));
|
||||||
|
auto editedDate = item->displayedEditDate();
|
||||||
if (!editedDate.isNull()) {
|
if (!editedDate.isNull()) {
|
||||||
dateText += '\n' + lng_edited_date(lt_date, editedDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
|
dateText += '\n' + lng_edited_date(lt_date, editedDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
|
||||||
}
|
}
|
||||||
if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
|
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||||
dateText += '\n' + lng_forwarded_date(lt_date, forwarded->_originalDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
|
dateText += '\n' + lng_forwarded_date(lt_date, forwarded->originalDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
|
||||||
}
|
}
|
||||||
return dateText;
|
return dateText;
|
||||||
}
|
}
|
||||||
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
|
} else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) {
|
||||||
if (App::hoveredItem()) {
|
if (const auto item = App::hoveredItem()) {
|
||||||
if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
|
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||||
return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
|
return forwarded->text.originalText(AllTextSelection, ExpandLinksNone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (auto lnk = ClickHandler::getActive()) {
|
} 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 "lang/lang_keys.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_service_layout.h"
|
#include "history/history_service_layout.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
#include "history/history_media_grouped.h"
|
#include "history/history_media_grouped.h"
|
||||||
|
@ -69,566 +70,6 @@ HistoryTextState::HistoryTextState(
|
||||||
, link(link) {
|
, 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() = default;
|
||||||
|
|
||||||
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer)
|
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer)
|
||||||
|
@ -714,6 +155,22 @@ void HistoryItem::finishEdition(int oldKeyboardTop) {
|
||||||
App::historyUpdateDependent(this);
|
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() {
|
void HistoryItem::invalidateChatsListEntry() {
|
||||||
if (App::main()) {
|
if (App::main()) {
|
||||||
App::main()->dlgUpdated(history()->peer, id);
|
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) {
|
void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||||
if (markup->inlineKeyboard) {
|
if (markup->inlineKeyboard) {
|
||||||
|
@ -798,6 +280,13 @@ void HistoryItem::addLogEntryOriginal(WebPageId localId, const QString &label, c
|
||||||
original->_page = std::make_unique<HistoryWebPage>(this, webpage);
|
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() {
|
void HistoryItem::destroy() {
|
||||||
if (isLogEntry()) {
|
if (isLogEntry()) {
|
||||||
Assert(detached());
|
Assert(detached());
|
||||||
|
@ -1122,6 +611,53 @@ QString HistoryItem::directLink() const {
|
||||||
return QString();
|
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 {
|
bool HistoryItem::hasOutLayout() const {
|
||||||
if (history()->peer->isSelf()) {
|
if (history()->peer->isSelf()) {
|
||||||
return !Has<HistoryMessageForwarded>();
|
return !Has<HistoryMessageForwarded>();
|
||||||
|
@ -1205,11 +741,19 @@ void HistoryItem::setUnreadBarCount(int count) {
|
||||||
|
|
||||||
void HistoryItem::setUnreadBarFreezed() {
|
void HistoryItem::setUnreadBarFreezed() {
|
||||||
Expects(!isLogEntry());
|
Expects(!isLogEntry());
|
||||||
if (auto bar = Get<HistoryMessageUnreadBar>()) {
|
|
||||||
|
if (const auto bar = Get<HistoryMessageUnreadBar>()) {
|
||||||
bar->_freezed = true;
|
bar->_freezed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageGroupId HistoryItem::groupId() const {
|
||||||
|
if (const auto group = Get<HistoryMessageGroup>()) {
|
||||||
|
return group->groupId;
|
||||||
|
}
|
||||||
|
return MessageGroupId::None;
|
||||||
|
}
|
||||||
|
|
||||||
bool HistoryItem::groupIdValidityChanged() {
|
bool HistoryItem::groupIdValidityChanged() {
|
||||||
if (Has<HistoryMessageGroup>()) {
|
if (Has<HistoryMessageGroup>()) {
|
||||||
if (_media && _media->canBeGrouped()) {
|
if (_media && _media->canBeGrouped()) {
|
||||||
|
@ -1284,6 +828,13 @@ void HistoryItem::resetGroupMedia(
|
||||||
setPendingInitDimensions();
|
setPendingInitDimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int HistoryItem::displayedDateHeight() const {
|
||||||
|
if (auto date = Get<HistoryMessageDate>()) {
|
||||||
|
return date->height();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int HistoryItem::marginTop() const {
|
int HistoryItem::marginTop() const {
|
||||||
auto result = 0;
|
auto result = 0;
|
||||||
if (!isHiddenByGroup()) {
|
if (!isHiddenByGroup()) {
|
||||||
|
@ -1300,6 +851,16 @@ int HistoryItem::marginTop() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::displayDate() const {
|
||||||
|
return Has<HistoryMessageDate>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::isEmpty() const {
|
||||||
|
return _text.isEmpty()
|
||||||
|
&& !_media
|
||||||
|
&& !Has<HistoryMessageLogEntryOriginal>();
|
||||||
|
}
|
||||||
|
|
||||||
int HistoryItem::marginBottom() const {
|
int HistoryItem::marginBottom() const {
|
||||||
return isHiddenByGroup() ? 0 : st::msgMargin.bottom();
|
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/flags.h"
|
||||||
#include "base/value_ordering.h"
|
#include "base/value_ordering.h"
|
||||||
|
|
||||||
|
struct MessageGroupId;
|
||||||
|
struct HistoryMessageGroup;
|
||||||
|
struct HistoryMessageReplyMarkup;
|
||||||
|
class ReplyKeyboard;
|
||||||
|
class HistoryMessage;
|
||||||
|
class HistoryMedia;
|
||||||
|
|
||||||
namespace base {
|
namespace base {
|
||||||
template <typename Enum>
|
template <typename Enum>
|
||||||
class enum_mask;
|
class enum_mask;
|
||||||
|
@ -68,8 +75,6 @@ protected:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class HistoryMessage;
|
|
||||||
|
|
||||||
enum HistoryCursorState {
|
enum HistoryCursorState {
|
||||||
HistoryDefaultCursorState,
|
HistoryDefaultCursorState,
|
||||||
HistoryInTextCursorState,
|
HistoryInTextCursorState,
|
||||||
|
@ -123,371 +128,8 @@ enum InfoDisplayType {
|
||||||
InfoDisplayOverBackground,
|
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
|
// HistoryMedia has a special owning smart pointer
|
||||||
// which regs/unregs this media to the holding HistoryItem
|
// which regs/unregs this media to the holding HistoryItem
|
||||||
class HistoryMedia;
|
|
||||||
class HistoryMediaPtr {
|
class HistoryMediaPtr {
|
||||||
public:
|
public:
|
||||||
HistoryMediaPtr();
|
HistoryMediaPtr();
|
||||||
|
@ -534,7 +176,10 @@ inline TextSelection shiftSelection(TextSelection selection, const Text &byText)
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|
||||||
class HistoryItem : public HistoryElement, public RuntimeComposer, public ClickHandlerHost {
|
class HistoryItem
|
||||||
|
: public HistoryElement
|
||||||
|
, public RuntimeComposer
|
||||||
|
, public ClickHandlerHost {
|
||||||
public:
|
public:
|
||||||
int resizeGetHeight(int newWidth) {
|
int resizeGetHeight(int newWidth) {
|
||||||
if (_flags & MTPDmessage_ClientFlag::f_pending_init_dimensions) {
|
if (_flags & MTPDmessage_ClientFlag::f_pending_init_dimensions) {
|
||||||
|
@ -564,12 +209,7 @@ public:
|
||||||
const base::flat_map<UserId, bool> &changes) {
|
const base::flat_map<UserId, bool> &changes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserData *viaBot() const {
|
UserData *viaBot() const;
|
||||||
if (auto via = Get<HistoryMessageVia>()) {
|
|
||||||
return via->_bot;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
UserData *getMessageBot() const {
|
UserData *getMessageBot() const {
|
||||||
if (auto bot = viaBot()) {
|
if (auto bot = viaBot()) {
|
||||||
return bot;
|
return bot;
|
||||||
|
@ -584,7 +224,10 @@ public:
|
||||||
bool isLogEntry() const {
|
bool isLogEntry() const {
|
||||||
return (id > ServerMaxMsgId);
|
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 {
|
not_null<History*> history() const {
|
||||||
return _history;
|
return _history;
|
||||||
|
@ -642,28 +285,9 @@ public:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool definesReplyKeyboard() const {
|
bool definesReplyKeyboard() const;
|
||||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const;
|
||||||
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 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 {
|
bool hasSwitchInlineButton() const {
|
||||||
return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button;
|
return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button;
|
||||||
}
|
}
|
||||||
|
@ -864,52 +488,17 @@ public:
|
||||||
virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize
|
virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
MsgId replyToId() const {
|
MsgId replyToId() const;
|
||||||
if (auto reply = Get<HistoryMessageReply>()) {
|
|
||||||
return reply->replyToId();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PeerData *author() const {
|
PeerData *author() const {
|
||||||
return isPost() ? history()->peer : from();
|
return isPost() ? history()->peer : from();
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime dateOriginal() const {
|
QDateTime dateOriginal() const;
|
||||||
if (auto forwarded = Get<HistoryMessageForwarded>()) {
|
PeerData *senderOriginal() const;
|
||||||
return forwarded->_originalDate;
|
PeerData *fromOriginal() const;
|
||||||
}
|
QString authorOriginal() const;
|
||||||
return date;
|
MsgId idOriginal() const;
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// count > 0 - creates the unread bar if necessary and
|
// count > 0 - creates the unread bar if necessary and
|
||||||
// sets unread messages count if bar is not freezed yet
|
// sets unread messages count if bar is not freezed yet
|
||||||
|
@ -939,12 +528,7 @@ public:
|
||||||
setPendingResize();
|
setPendingResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
int displayedDateHeight() const {
|
int displayedDateHeight() const;
|
||||||
if (auto date = Get<HistoryMessageDate>()) {
|
|
||||||
return date->height();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int marginTop() const;
|
int marginTop() const;
|
||||||
int marginBottom() const;
|
int marginBottom() const;
|
||||||
bool isAttachedToPrevious() const {
|
bool isAttachedToPrevious() const {
|
||||||
|
@ -953,27 +537,18 @@ public:
|
||||||
bool isAttachedToNext() const {
|
bool isAttachedToNext() const {
|
||||||
return _flags & MTPDmessage_ClientFlag::f_attach_to_next;
|
return _flags & MTPDmessage_ClientFlag::f_attach_to_next;
|
||||||
}
|
}
|
||||||
bool displayDate() const {
|
bool displayDate() const;
|
||||||
return Has<HistoryMessageDate>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isInOneDayWithPrevious() const {
|
bool isInOneDayWithPrevious() const {
|
||||||
return !isEmpty() && !displayDate();
|
return !isEmpty() && !displayDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isEmpty() const {
|
bool isEmpty() const;
|
||||||
return _text.isEmpty() && !_media && !Has<HistoryMessageLogEntryOriginal>();
|
|
||||||
}
|
|
||||||
bool isHiddenByGroup() const {
|
bool isHiddenByGroup() const {
|
||||||
return _flags & MTPDmessage_ClientFlag::f_hidden_by_group;
|
return _flags & MTPDmessage_ClientFlag::f_hidden_by_group;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageGroupId groupId() const {
|
MessageGroupId groupId() const;
|
||||||
if (const auto group = Get<HistoryMessageGroup>()) {
|
|
||||||
return group->groupId;
|
|
||||||
}
|
|
||||||
return MessageGroupId::None;
|
|
||||||
}
|
|
||||||
bool groupIdValidityChanged();
|
bool groupIdValidityChanged();
|
||||||
void validateGroupId() {
|
void validateGroupId() {
|
||||||
// Just ignore the result.
|
// Just ignore the result.
|
||||||
|
@ -1089,20 +664,8 @@ protected:
|
||||||
const ReplyKeyboard *inlineReplyKeyboard() const {
|
const ReplyKeyboard *inlineReplyKeyboard() const {
|
||||||
return const_cast<HistoryItem*>(this)->inlineReplyKeyboard();
|
return const_cast<HistoryItem*>(this)->inlineReplyKeyboard();
|
||||||
}
|
}
|
||||||
HistoryMessageReplyMarkup *inlineReplyMarkup() {
|
HistoryMessageReplyMarkup *inlineReplyMarkup();
|
||||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
ReplyKeyboard *inlineReplyKeyboard();
|
||||||
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
|
|
||||||
return markup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
ReplyKeyboard *inlineReplyKeyboard() {
|
|
||||||
if (auto markup = inlineReplyMarkup()) {
|
|
||||||
return markup->inlineKeyboard.get();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
void invalidateChatsListEntry();
|
void invalidateChatsListEntry();
|
||||||
|
|
||||||
[[nodiscard]] TextSelection skipTextSelection(
|
[[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
|
#pragma once
|
||||||
|
|
||||||
|
struct HistoryMessageEdited;
|
||||||
|
|
||||||
namespace base {
|
namespace base {
|
||||||
template <typename Enum>
|
template <typename Enum>
|
||||||
class enum_mask;
|
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_media_grouped.h"
|
||||||
|
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
#include "storage/storage_shared_media.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/confirm_box.h"
|
||||||
#include "boxes/add_contact_box.h"
|
#include "boxes/add_contact_box.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_location_manager.h"
|
#include "history/history_location_manager.h"
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
#include "window/main_window.h"
|
#include "window/main_window.h"
|
||||||
|
@ -1364,39 +1365,6 @@ ImagePtr HistoryVideo::replyPreview() {
|
||||||
return _data->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(
|
HistoryDocument::HistoryDocument(
|
||||||
not_null<HistoryItem*> parent,
|
not_null<HistoryItem*> parent,
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
|
@ -1925,6 +1893,26 @@ QString HistoryDocument::inDialogsText() const {
|
||||||
return result;
|
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 HistoryDocument::selectedText(TextSelection selection) const {
|
||||||
TextWithEntities result;
|
TextWithEntities result;
|
||||||
buildStringRepresentation([&result, selection](const QString &type, const QString &fileName, const Text &caption) {
|
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);
|
return needReSetInlineResultDocument(media, _data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextWithEntities HistoryDocument::getCaption() const {
|
||||||
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
|
return captioned->_caption.originalTextWithEntities();
|
||||||
|
}
|
||||||
|
return TextWithEntities();
|
||||||
|
}
|
||||||
|
|
||||||
ImagePtr HistoryDocument::replyPreview() {
|
ImagePtr HistoryDocument::replyPreview() {
|
||||||
return _data->makeReplyPreview();
|
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 rectw = _width - usew - st::msgReplyPadding.left();
|
||||||
auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
|
auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
|
||||||
auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
|
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);
|
auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height);
|
||||||
if (forwarded) {
|
if (forwarded) {
|
||||||
recth += forwardedHeight;
|
recth += forwardedHeight;
|
||||||
|
@ -2534,11 +2529,11 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
|
||||||
if (forwarded) {
|
if (forwarded) {
|
||||||
p.setTextPalette(st::serviceTextPalette);
|
p.setTextPalette(st::serviceTextPalette);
|
||||||
auto breakEverywhere = (forwardedHeightReal > forwardedHeight);
|
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();
|
p.restoreTextPalette();
|
||||||
} else if (via) {
|
} else if (via) {
|
||||||
p.setFont(st::msgDateFont);
|
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);
|
int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
|
||||||
recty += skip;
|
recty += skip;
|
||||||
}
|
}
|
||||||
|
@ -2632,7 +2627,7 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request)
|
||||||
auto rectw = width - usew - st::msgReplyPadding.left();
|
auto rectw = width - usew - st::msgReplyPadding.left();
|
||||||
auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
|
auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right());
|
||||||
auto recth = st::msgReplyPadding.top() + st::msgReplyPadding.bottom();
|
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);
|
auto forwardedHeight = qMin(forwardedHeightReal, kMaxGifForwardedBarLines * st::msgServiceNameFont->height);
|
||||||
if (forwarded) {
|
if (forwarded) {
|
||||||
recth += forwardedHeight;
|
recth += forwardedHeight;
|
||||||
|
@ -2653,7 +2648,7 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request)
|
||||||
if (breakEverywhere) {
|
if (breakEverywhere) {
|
||||||
textRequest.flags |= Text::StateRequest::Flag::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()),
|
point - QPoint(rectx + st::msgReplyPadding.left(), recty + st::msgReplyPadding.top()),
|
||||||
innerw,
|
innerw,
|
||||||
textRequest));
|
textRequest));
|
||||||
|
@ -2671,7 +2666,7 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request)
|
||||||
} else if (via) {
|
} else if (via) {
|
||||||
auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
|
auto viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
|
||||||
if (QRect(rectx, recty, rectw, viah).contains(point)) {
|
if (QRect(rectx, recty, rectw, viah).contains(point)) {
|
||||||
result.link = via->_lnk;
|
result.link = via->link;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
|
auto skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
|
||||||
|
@ -2773,6 +2768,13 @@ Storage::SharedMediaTypesMask HistoryGif::sharedMediaTypes() const {
|
||||||
return Type::File;
|
return Type::File;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int HistoryGif::additionalWidth() const {
|
||||||
|
return additionalWidth(
|
||||||
|
_parent->Get<HistoryMessageVia>(),
|
||||||
|
_parent->Get<HistoryMessageReply>(),
|
||||||
|
_parent->Get<HistoryMessageForwarded>());
|
||||||
|
}
|
||||||
|
|
||||||
QString HistoryGif::mediaTypeString() const {
|
QString HistoryGif::mediaTypeString() const {
|
||||||
return _data->isVideoMessage()
|
return _data->isVideoMessage()
|
||||||
? lang(lng_in_dlg_video_message)
|
? 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 HistoryGif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
if (forwarded) {
|
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) {
|
} 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) {
|
if (reply) {
|
||||||
accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
|
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();
|
rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right();
|
||||||
if (via) {
|
if (via) {
|
||||||
p.setFont(st::msgDateFont);
|
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);
|
int skip = st::msgServiceNameFont->height + (reply ? st::msgReplyPadding.top() : 0);
|
||||||
recty += skip;
|
recty += skip;
|
||||||
}
|
}
|
||||||
|
@ -3150,7 +3152,7 @@ HistoryTextState HistorySticker::getState(QPoint point, HistoryStateRequest requ
|
||||||
if (via) {
|
if (via) {
|
||||||
int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
|
int viah = st::msgReplyPadding.top() + st::msgServiceNameFont->height + (reply ? 0 : st::msgReplyPadding.bottom());
|
||||||
if (QRect(rectx, recty, rectw, viah).contains(point)) {
|
if (QRect(rectx, recty, rectw, viah).contains(point)) {
|
||||||
result.link = via->_lnk;
|
result.link = via->link;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
int skip = st::msgServiceNameFont->height + (reply ? 2 * st::msgReplyPadding.top() : 0);
|
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 HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
if (via) {
|
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) {
|
if (reply) {
|
||||||
accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
|
accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth());
|
||||||
|
@ -3244,6 +3246,12 @@ int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryM
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int HistorySticker::additionalWidth() const {
|
||||||
|
return additionalWidth(
|
||||||
|
_parent->Get<HistoryMessageVia>(),
|
||||||
|
_parent->Get<HistoryMessageReply>());
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
ClickHandlerPtr sendMessageClickHandler(PeerData *peer) {
|
ClickHandlerPtr sendMessageClickHandler(PeerData *peer) {
|
||||||
|
@ -4104,6 +4112,10 @@ void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool p
|
||||||
_attach->clickHandlerPressedChanged(p, pressed);
|
_attach->clickHandlerPressedChanged(p, pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool HistoryWebPage::isDisplayed() const {
|
||||||
|
return !_data->pendingTill
|
||||||
|
&& !_parent->Has<HistoryMessageLogEntryOriginal>();
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryWebPage::attachToParent() {
|
void HistoryWebPage::attachToParent() {
|
||||||
App::regWebPageItem(_data, _parent);
|
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_web_page.h"
|
||||||
#include "data/data_game.h"
|
#include "data/data_game.h"
|
||||||
|
|
||||||
|
class ReplyMarkupClickHandler;
|
||||||
|
struct HistoryDocumentNamed;
|
||||||
|
struct HistoryMessageVia;
|
||||||
|
struct HistoryMessageReply;
|
||||||
|
struct HistoryMessageForwarded;
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
namespace Clip {
|
namespace Clip {
|
||||||
class Playback;
|
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 {
|
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
|
||||||
public:
|
public:
|
||||||
HistoryDocument(
|
HistoryDocument(
|
||||||
|
@ -497,22 +441,10 @@ public:
|
||||||
void updatePressed(QPoint point) override;
|
void updatePressed(QPoint point) override;
|
||||||
|
|
||||||
[[nodiscard]] TextSelection adjustSelection(
|
[[nodiscard]] TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const override {
|
TextSelectType type) const override;
|
||||||
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
|
uint16 fullSelectionLength() const override;
|
||||||
return captioned->_caption.adjustSelection(selection, type);
|
bool hasTextForCopy() const override;
|
||||||
}
|
|
||||||
return selection;
|
|
||||||
}
|
|
||||||
uint16 fullSelectionLength() const override {
|
|
||||||
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
|
|
||||||
return captioned->_caption.length();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
bool hasTextForCopy() const override {
|
|
||||||
return Has<HistoryDocumentCaptioned>();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString notificationText() const override;
|
QString notificationText() const override;
|
||||||
QString inDialogsText() const override;
|
QString inDialogsText() const override;
|
||||||
|
@ -541,12 +473,7 @@ public:
|
||||||
}
|
}
|
||||||
ImagePtr replyPreview() override;
|
ImagePtr replyPreview() override;
|
||||||
|
|
||||||
TextWithEntities getCaption() const override {
|
TextWithEntities getCaption() const override;
|
||||||
if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
|
|
||||||
return captioned->_caption.originalTextWithEntities();
|
|
||||||
}
|
|
||||||
return TextWithEntities();
|
|
||||||
}
|
|
||||||
bool needsBubble() const override {
|
bool needsBubble() const override {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -693,10 +620,11 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const;
|
int additionalWidth(
|
||||||
int additionalWidth() const {
|
const HistoryMessageVia *via,
|
||||||
return additionalWidth(_parent->Get<HistoryMessageVia>(), _parent->Get<HistoryMessageReply>(), _parent->Get<HistoryMessageForwarded>());
|
const HistoryMessageReply *reply,
|
||||||
}
|
const HistoryMessageForwarded *forwarded) const;
|
||||||
|
int additionalWidth() const;
|
||||||
QString mediaTypeString() const;
|
QString mediaTypeString() const;
|
||||||
bool isSeparateRoundVideo() const;
|
bool isSeparateRoundVideo() const;
|
||||||
|
|
||||||
|
@ -777,9 +705,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
|
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
|
||||||
int additionalWidth() const {
|
int additionalWidth() const;
|
||||||
return additionalWidth(_parent->Get<HistoryMessageVia>(), _parent->Get<HistoryMessageReply>());
|
|
||||||
}
|
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
|
|
||||||
int16 _pixw = 1;
|
int16 _pixw = 1;
|
||||||
|
@ -978,9 +904,7 @@ public:
|
||||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||||
|
|
||||||
bool isDisplayed() const override {
|
bool isDisplayed() const override;
|
||||||
return !_data->pendingTill && !_parent->Has<HistoryMessageLogEntryOriginal>();
|
|
||||||
}
|
|
||||||
PhotoData *getPhoto() const override {
|
PhotoData *getPhoto() const override {
|
||||||
return _attach ? _attach->getPhoto() : nullptr;
|
return _attach ? _attach->getPhoto() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_location_manager.h"
|
#include "history/history_location_manager.h"
|
||||||
#include "history/history_service_layout.h"
|
#include "history/history_service_layout.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
|
@ -46,6 +47,97 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kPinnedMessageTextLimit = 16;
|
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() {
|
inline void initTextOptions() {
|
||||||
_historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir();
|
_historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir();
|
||||||
_textDlgOptions.maxw = st::columnMaximalWidthLeft * 2;
|
_textDlgOptions.maxw = st::columnMaximalWidthLeft * 2;
|
||||||
|
@ -321,7 +413,8 @@ void HistoryInitMessages() {
|
||||||
initTextOptions();
|
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) {
|
return [dependent = msgId](ChannelData *channel, MsgId msgId) {
|
||||||
if (auto item = App::histItemById(dependent)) {
|
if (auto item = App::histItemById(dependent)) {
|
||||||
item->updateDependencyItem();
|
item->updateDependencyItem();
|
||||||
|
@ -363,293 +456,26 @@ QString GetErrorTextForForward(
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryMessageVia::create(UserId userId) {
|
struct HistoryMessage::CreateConfig {
|
||||||
_bot = App::user(peerFromUser(userId));
|
MsgId replyTo = 0;
|
||||||
_maxWidth = st::msgServiceNameFont->width(lng_inline_bot_via(lt_inline_bot, '@' + _bot->username));
|
UserId viaBotId = 0;
|
||||||
_lnk = std::make_shared<LambdaClickHandler>([bot = _bot] {
|
int viewsCount = -1;
|
||||||
App::insertBotCommand('@' + bot->username);
|
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 {
|
// For messages created from MTP structs.
|
||||||
if (availw < 0) {
|
const MTPReplyMarkup *mtpMarkup = nullptr;
|
||||||
_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::create(const QString &author, const QString &date) {
|
// For messages created from existing messages (forwarded).
|
||||||
auto time = qsl(", ") + date;
|
const HistoryMessageReplyMarkup *inlineMarkup = nullptr;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
HistoryMessage::HistoryMessage(
|
HistoryMessage::HistoryMessage(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
|
@ -922,12 +748,26 @@ void HistoryMessage::updateMediaInBubbleState() {
|
||||||
_media->setInBubbleState(computeState());
|
_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 {
|
not_null<PeerData*> HistoryMessage::displayFrom() const {
|
||||||
return history()->peer->isSelf()
|
return history()->peer->isSelf()
|
||||||
? senderOriginal()
|
? senderOriginal()
|
||||||
: author();
|
: author();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HistoryMessage::updateDependencyItem() {
|
||||||
|
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||||
|
return reply->updateData(this, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryMessage::updateAdminBadgeState() {
|
void HistoryMessage::updateAdminBadgeState() {
|
||||||
auto hasAdminBadge = [&] {
|
auto hasAdminBadge = [&] {
|
||||||
if (auto channel = history()->peer->asChannel()) {
|
if (auto channel = history()->peer->asChannel()) {
|
||||||
|
@ -1018,7 +858,7 @@ bool HistoryMessage::displayFastShare() const {
|
||||||
bool HistoryMessage::displayGoToOriginal() const {
|
bool HistoryMessage::displayGoToOriginal() const {
|
||||||
if (_history->peer->isSelf()) {
|
if (_history->peer->isSelf()) {
|
||||||
if (auto forwarded = Get<HistoryMessageForwarded>()) {
|
if (auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||||
return forwarded->_savedFromPeer && forwarded->_savedFromMsgId;
|
return forwarded->savedFromPeer && forwarded->savedFromMsgId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -1084,13 +924,16 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||||
if (const auto edited = Get<HistoryMessageEdited>()) {
|
if (const auto edited = Get<HistoryMessageEdited>()) {
|
||||||
edited->date = config.editDate;
|
edited->date = config.editDate;
|
||||||
}
|
}
|
||||||
|
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
|
||||||
|
msgsigned->author = config.author;
|
||||||
|
}
|
||||||
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||||
forwarded->_originalDate = config.originalDate;
|
forwarded->originalDate = config.originalDate;
|
||||||
forwarded->_originalSender = App::peer(config.senderOriginal);
|
forwarded->originalSender = App::peer(config.senderOriginal);
|
||||||
forwarded->_originalId = config.originalId;
|
forwarded->originalId = config.originalId;
|
||||||
forwarded->_originalAuthor = config.authorOriginal;
|
forwarded->originalAuthor = config.authorOriginal;
|
||||||
forwarded->_savedFromPeer = App::peerLoaded(config.savedFromPeer);
|
forwarded->savedFromPeer = App::peerLoaded(config.savedFromPeer);
|
||||||
forwarded->_savedFromMsgId = config.savedFromMsgId;
|
forwarded->savedFromMsgId = config.savedFromMsgId;
|
||||||
}
|
}
|
||||||
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||||
if (config.mtpMarkup) {
|
if (config.mtpMarkup) {
|
||||||
|
@ -1224,7 +1067,7 @@ void HistoryMessage::replaceBuyWithReceiptInMarkup() {
|
||||||
if (auto markup = inlineReplyMarkup()) {
|
if (auto markup = inlineReplyMarkup()) {
|
||||||
for (auto &row : markup->rows) {
|
for (auto &row : markup->rows) {
|
||||||
for (auto &button : row) {
|
for (auto &button : row) {
|
||||||
if (button.type == HistoryMessageReplyMarkup::Button::Type::Buy) {
|
if (button.type == HistoryMessageMarkupButton::Type::Buy) {
|
||||||
button.text = lang(lng_payments_receipt_button);
|
button.text = lang(lng_payments_receipt_button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1314,7 +1157,7 @@ void HistoryMessage::initDimensions() {
|
||||||
+ displayFrom()->nameText.maxWidth()
|
+ displayFrom()->nameText.maxWidth()
|
||||||
+ st::msgPadding.right();
|
+ st::msgPadding.right();
|
||||||
if (via && !forwarded) {
|
if (via && !forwarded) {
|
||||||
namew += st::msgServiceFont->spacew + via->_maxWidth;
|
namew += st::msgServiceFont->spacew + via->maxWidth;
|
||||||
}
|
}
|
||||||
if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
|
if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
|
||||||
auto badgeWidth = st::msgServiceFont->width(
|
auto badgeWidth = st::msgServiceFont->width(
|
||||||
|
@ -1323,19 +1166,19 @@ void HistoryMessage::initDimensions() {
|
||||||
}
|
}
|
||||||
accumulate_max(_maxw, namew);
|
accumulate_max(_maxw, namew);
|
||||||
} else if (via && !forwarded) {
|
} 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) {
|
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) {
|
if (via) {
|
||||||
namew += st::msgServiceFont->spacew + via->_maxWidth;
|
namew += st::msgServiceFont->spacew + via->maxWidth;
|
||||||
}
|
}
|
||||||
accumulate_max(_maxw, namew);
|
accumulate_max(_maxw, namew);
|
||||||
}
|
}
|
||||||
if (reply) {
|
if (reply) {
|
||||||
auto replyw = st::msgPadding.left() + reply->_maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right();
|
auto replyw = st::msgPadding.left() + reply->maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right();
|
||||||
if (reply->_replyToVia) {
|
if (reply->replyToVia) {
|
||||||
replyw += st::msgServiceFont->spacew + reply->_replyToVia->_maxWidth;
|
replyw += st::msgServiceFont->spacew + reply->replyToVia->maxWidth;
|
||||||
}
|
}
|
||||||
accumulate_max(_maxw, replyw);
|
accumulate_max(_maxw, replyw);
|
||||||
}
|
}
|
||||||
|
@ -1486,11 +1329,11 @@ void HistoryMessage::refreshEditedBadge() {
|
||||||
if (edited) {
|
if (edited) {
|
||||||
edited->refresh(dateText, !editDate.isNull());
|
edited->refresh(dateText, !editDate.isNull());
|
||||||
}
|
}
|
||||||
if (auto msgsigned = Get<HistoryMessageSigned>()) {
|
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
|
||||||
const auto text = (!edited || editDate.isNull())
|
const auto text = (!edited || editDate.isNull())
|
||||||
? dateText
|
? dateText
|
||||||
: edited->text.originalText();
|
: edited->text.originalText();
|
||||||
msgsigned->create(msgsigned->_author, text);
|
msgsigned->refresh(text);
|
||||||
}
|
}
|
||||||
initTime();
|
initTime();
|
||||||
}
|
}
|
||||||
|
@ -1504,7 +1347,7 @@ bool HistoryMessage::displayForwardedFrom() const {
|
||||||
|| !_media
|
|| !_media
|
||||||
|| !_media->isDisplayed()
|
|| !_media->isDisplayed()
|
||||||
|| !_media->hideForwardedFrom()
|
|| !_media->hideForwardedFrom()
|
||||||
|| forwarded->_originalSender->isChannel();
|
|| forwarded->originalSender->isChannel();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1604,7 +1447,7 @@ TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
|
||||||
}
|
}
|
||||||
if (auto forwarded = Get<HistoryMessageForwarded>()) {
|
if (auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||||
if (selection == FullSelection) {
|
if (selection == FullSelection) {
|
||||||
auto fwdinfo = forwarded->_text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
|
auto fwdinfo = forwarded->text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
|
||||||
auto wrapped = TextWithEntities();
|
auto wrapped = TextWithEntities();
|
||||||
wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
|
wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
|
||||||
wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.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();
|
dateX += HistoryMessage::timeLeft();
|
||||||
|
|
||||||
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
|
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()) {
|
} else if (const auto edited = displayedEditBadge()) {
|
||||||
edited->text.drawElided(p, dateX, dateY, _timeWidth);
|
edited->text.drawElided(p, dateX, dateY, _timeWidth);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2062,8 +1905,8 @@ void HistoryMessage::paintFromName(Painter &p, QRect &trect, bool selected) cons
|
||||||
if (via && !forwarded && availableWidth > 0) {
|
if (via && !forwarded && availableWidth > 0) {
|
||||||
auto outbg = hasOutLayout();
|
auto outbg = hasOutLayout();
|
||||||
p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
|
p.setPen(selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg));
|
||||||
p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->_text);
|
p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->text);
|
||||||
auto skipWidth = via->_width + st::msgServiceFont->spacew;
|
auto skipWidth = via->width + st::msgServiceFont->spacew;
|
||||||
availableLeft += skipWidth;
|
availableLeft += skipWidth;
|
||||||
availableWidth -= skipWidth;
|
availableWidth -= skipWidth;
|
||||||
}
|
}
|
||||||
|
@ -2088,12 +1931,12 @@ void HistoryMessage::paintForwardedInfo(Painter &p, QRect &trect, bool selected)
|
||||||
p.setFont(serviceFont);
|
p.setFont(serviceFont);
|
||||||
|
|
||||||
auto forwarded = Get<HistoryMessageForwarded>();
|
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));
|
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));
|
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>()) {
|
if (auto via = Get<HistoryMessageVia>()) {
|
||||||
p.setFont(st::msgServiceNameFont);
|
p.setFont(st::msgServiceNameFont);
|
||||||
p.setPen(selected ? (hasOutLayout() ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (hasOutLayout() ? st::msgOutServiceFg : st::msgInServiceFg));
|
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);
|
trect.setY(trect.y() + st::msgServiceNameFont->height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2236,7 +2079,7 @@ int HistoryMessage::performResizeGetHeight() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayForwardedFrom()) {
|
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;
|
_height += fwdheight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2403,8 +2246,8 @@ ClickHandlerPtr HistoryMessage::rightActionLink() const {
|
||||||
if (!_rightActionLink) {
|
if (!_rightActionLink) {
|
||||||
const auto itemId = fullId();
|
const auto itemId = fullId();
|
||||||
const auto forwarded = Get<HistoryMessageForwarded>();
|
const auto forwarded = Get<HistoryMessageForwarded>();
|
||||||
const auto savedFromPeer = forwarded ? forwarded->_savedFromPeer : nullptr;
|
const auto savedFromPeer = forwarded ? forwarded->savedFromPeer : nullptr;
|
||||||
const auto savedFromMsgId = forwarded ? forwarded->_savedFromMsgId : 0;
|
const auto savedFromMsgId = forwarded ? forwarded->savedFromMsgId : 0;
|
||||||
_rightActionLink = std::make_shared<LambdaClickHandler>([=] {
|
_rightActionLink = std::make_shared<LambdaClickHandler>([=] {
|
||||||
if (auto item = App::histItemById(itemId)) {
|
if (auto item = App::histItemById(itemId)) {
|
||||||
if (savedFromPeer && savedFromMsgId) {
|
if (savedFromPeer && savedFromMsgId) {
|
||||||
|
@ -2442,7 +2285,7 @@ void HistoryMessage::updatePressed(QPoint point) {
|
||||||
if (displayFromName()) trect.setTop(trect.top() + st::msgNameFont->height);
|
if (displayFromName()) trect.setTop(trect.top() + st::msgNameFont->height);
|
||||||
if (displayForwardedFrom()) {
|
if (displayForwardedFrom()) {
|
||||||
auto forwarded = Get<HistoryMessageForwarded>();
|
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);
|
trect.setTop(trect.top() + fwdheight);
|
||||||
}
|
}
|
||||||
if (Get<HistoryMessageReply>()) {
|
if (Get<HistoryMessageReply>()) {
|
||||||
|
@ -2485,8 +2328,8 @@ bool HistoryMessage::getStateFromName(
|
||||||
}
|
}
|
||||||
auto forwarded = Get<HistoryMessageForwarded>();
|
auto forwarded = Get<HistoryMessageForwarded>();
|
||||||
auto via = Get<HistoryMessageVia>();
|
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) {
|
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;
|
outResult->link = via->link;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2502,14 +2345,14 @@ bool HistoryMessage::getStateForwardedInfo(
|
||||||
const HistoryStateRequest &request) const {
|
const HistoryStateRequest &request) const {
|
||||||
if (displayForwardedFrom()) {
|
if (displayForwardedFrom()) {
|
||||||
auto forwarded = Get<HistoryMessageForwarded>();
|
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) {
|
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();
|
auto textRequest = request.forText();
|
||||||
if (breakEverywhere) {
|
if (breakEverywhere) {
|
||||||
textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
|
textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
|
||||||
}
|
}
|
||||||
*outResult = HistoryTextState(this, forwarded->_text.getState(
|
*outResult = HistoryTextState(this, forwarded->text.getState(
|
||||||
point - trect.topLeft(),
|
point - trect.topLeft(),
|
||||||
trect.width(),
|
trect.width(),
|
||||||
textRequest));
|
textRequest));
|
||||||
|
@ -2550,8 +2393,8 @@ bool HistoryMessage::getStateViaBotIdInfo(
|
||||||
not_null<HistoryTextState*> outResult) const {
|
not_null<HistoryTextState*> outResult) const {
|
||||||
if (!displayFromName() && !Has<HistoryMessageForwarded>()) {
|
if (!displayFromName() && !Has<HistoryMessageForwarded>()) {
|
||||||
if (auto via = Get<HistoryMessageVia>()) {
|
if (auto via = Get<HistoryMessageVia>()) {
|
||||||
if (QRect(trect.x(), trect.y(), via->_width, st::msgNameFont->height).contains(point)) {
|
if (QRect(trect.x(), trect.y(), via->width, st::msgNameFont->height).contains(point)) {
|
||||||
outResult->link = via->_lnk;
|
outResult->link = via->link;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
trect.setTop(trect.top() + st::msgNameFont->height);
|
trect.setTop(trect.top() + st::msgNameFont->height);
|
||||||
|
|
|
@ -20,15 +20,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "history/history_item.h"
|
||||||
|
|
||||||
|
struct HistoryMessageEdited;
|
||||||
|
|
||||||
void HistoryInitMessages();
|
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);
|
MTPDmessage::Flags NewMessageFlags(not_null<PeerData*> peer);
|
||||||
QString GetErrorTextForForward(
|
QString GetErrorTextForForward(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const HistoryItemsList &items);
|
const HistoryItemsList &items);
|
||||||
void FastShareMessage(not_null<HistoryItem*> item);
|
void FastShareMessage(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> {
|
class HistoryMessage
|
||||||
|
: public HistoryItem
|
||||||
|
, private HistoryItemInstantiated<HistoryMessage> {
|
||||||
public:
|
public:
|
||||||
static not_null<HistoryMessage*> create(
|
static not_null<HistoryMessage*> create(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
|
@ -218,21 +225,9 @@ public:
|
||||||
return _timeWidth;
|
return _timeWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int viewsCount() const override {
|
int viewsCount() const override;
|
||||||
if (auto views = Get<HistoryMessageViews>()) {
|
|
||||||
return views->_views;
|
|
||||||
}
|
|
||||||
return HistoryItem::viewsCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
not_null<PeerData*> displayFrom() const;
|
not_null<PeerData*> displayFrom() const;
|
||||||
|
bool updateDependencyItem() override;
|
||||||
bool updateDependencyItem() override {
|
|
||||||
if (auto reply = Get<HistoryMessageReply>()) {
|
|
||||||
return reply->updateData(this, true);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
MsgId dependencyMsgId() const override {
|
MsgId dependencyMsgId() const override {
|
||||||
return replyToId();
|
return replyToId();
|
||||||
}
|
}
|
||||||
|
@ -372,47 +367,10 @@ private:
|
||||||
mutable ClickHandlerPtr _rightActionLink;
|
mutable ClickHandlerPtr _rightActionLink;
|
||||||
mutable int32 _fromNameVersion = 0;
|
mutable int32 _fromNameVersion = 0;
|
||||||
|
|
||||||
struct CreateConfig {
|
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;
|
|
||||||
};
|
|
||||||
void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup);
|
void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup);
|
||||||
void createComponents(const CreateConfig &config);
|
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 updateMediaInBubbleState();
|
||||||
void updateAdminBadgeState();
|
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_service_layout.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "auth_session.h"
|
#include "auth_session.h"
|
||||||
#include "window/notifications_manager.h"
|
#include "window/notifications_manager.h"
|
||||||
#include "storage/storage_shared_media.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_media_types.h"
|
||||||
#include "history/history_drag_area.h"
|
#include "history/history_drag_area.h"
|
||||||
#include "history/history_inner_widget.h"
|
#include "history/history_inner_widget.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "profile/profile_block_group_members.h"
|
#include "profile/profile_block_group_members.h"
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
#include "core/click_handler_types.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) {
|
if (msg->id < 0 || _peer != msg->history()->peer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3329,8 +3334,14 @@ void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button
|
||||||
|
|
||||||
auto bot = msg->getMessageBot();
|
auto bot = msg->getMessageBot();
|
||||||
|
|
||||||
using ButtonType = HistoryMessageReplyMarkup::Button::Type;
|
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||||
BotCallbackInfo info = { bot, msg->fullId(), row, col, (button->type == ButtonType::Game) };
|
BotCallbackInfo info = {
|
||||||
|
bot,
|
||||||
|
msg->fullId(),
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
(button->type == ButtonType::Game)
|
||||||
|
};
|
||||||
auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0);
|
auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0);
|
||||||
QByteArray sendData;
|
QByteArray sendData;
|
||||||
if (info.game) {
|
if (info.game) {
|
||||||
|
|
|
@ -74,6 +74,7 @@ class BotKeyboard;
|
||||||
class MessageField;
|
class MessageField;
|
||||||
class HistoryInner;
|
class HistoryInner;
|
||||||
class HistoryTopBarWidget;
|
class HistoryTopBarWidget;
|
||||||
|
struct HistoryMessageMarkupButton;
|
||||||
|
|
||||||
class ReportSpamPanel : public TWidget {
|
class ReportSpamPanel : public TWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -332,7 +333,11 @@ public:
|
||||||
bool wheelEventFromFloatPlayer(QEvent *e) override;
|
bool wheelEventFromFloatPlayer(QEvent *e) override;
|
||||||
QRect rectForFloatPlayer() const 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();
|
PeerData *ui_getPeerForMouseAction();
|
||||||
|
|
||||||
|
|
|
@ -1612,8 +1612,12 @@ void MainWidget::hideSingleUseKeyboard(PeerData *peer, MsgId replyTo) {
|
||||||
_history->hideSingleUseKeyboard(peer, replyTo);
|
_history->hideSingleUseKeyboard(peer, replyTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col) {
|
void MainWidget::app_sendBotCallback(
|
||||||
_history->app_sendBotCallback(button, msg, row, col);
|
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) {
|
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 "base/weak_ptr.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
|
|
||||||
|
struct HistoryMessageMarkupButton;
|
||||||
|
class MainWindow;
|
||||||
|
class ConfirmBox;
|
||||||
|
class DialogsWidget;
|
||||||
|
class HistoryWidget;
|
||||||
|
class HistoryHider;
|
||||||
|
class StackItem;
|
||||||
|
|
||||||
namespace Notify {
|
namespace Notify {
|
||||||
struct PeerUpdate;
|
struct PeerUpdate;
|
||||||
} // namespace Notify
|
} // namespace Notify
|
||||||
|
@ -66,14 +74,6 @@ class Call;
|
||||||
class TopBar;
|
class TopBar;
|
||||||
} // namespace Calls
|
} // namespace Calls
|
||||||
|
|
||||||
class MainWindow;
|
|
||||||
class ConfirmBox;
|
|
||||||
class DialogsWidget;
|
|
||||||
class HistoryWidget;
|
|
||||||
class HistoryHider;
|
|
||||||
|
|
||||||
class StackItem;
|
|
||||||
|
|
||||||
namespace InlineBots {
|
namespace InlineBots {
|
||||||
namespace Layout {
|
namespace Layout {
|
||||||
class ItemBase;
|
class ItemBase;
|
||||||
|
@ -336,7 +336,11 @@ public:
|
||||||
|
|
||||||
void documentLoadProgress(DocumentData *document);
|
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(
|
void ui_showPeerHistory(
|
||||||
PeerId peer,
|
PeerId peer,
|
||||||
|
|
|
@ -35,6 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "ui/effects/round_checkbox.h"
|
#include "ui/effects/round_checkbox.h"
|
||||||
|
|
||||||
namespace Overview {
|
namespace Overview {
|
||||||
|
@ -751,7 +752,7 @@ const style::RoundCheckbox &Voice::checkboxStyle() const {
|
||||||
|
|
||||||
void Voice::updateName() {
|
void Voice::updateName() {
|
||||||
auto version = 0;
|
auto version = 0;
|
||||||
if (auto forwarded = parent()->Get<HistoryMessageForwarded>()) {
|
if (const auto forwarded = parent()->Get<HistoryMessageForwarded>()) {
|
||||||
if (parent()->fromOriginal()->isChannel()) {
|
if (parent()->fromOriginal()->isChannel()) {
|
||||||
_name.setText(st::semiboldTextStyle, lng_forwarded_channel(lt_channel, App::peerName(parent()->fromOriginal())), _textNameOptions);
|
_name.setText(st::semiboldTextStyle, lng_forwarded_channel(lt_channel, App::peerName(parent()->fromOriginal())), _textNameOptions);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "window/notifications_manager_default.h"
|
#include "window/notifications_manager_default.h"
|
||||||
#include "media/media_audio_track.h"
|
#include "media/media_audio_track.h"
|
||||||
#include "media/media_audio.h"
|
#include "media/media_audio.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
|
|
@ -215,6 +215,8 @@
|
||||||
<(src_loc)/history/history_drag_area.h
|
<(src_loc)/history/history_drag_area.h
|
||||||
<(src_loc)/history/history_item.cpp
|
<(src_loc)/history/history_item.cpp
|
||||||
<(src_loc)/history/history_item.h
|
<(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.cpp
|
||||||
<(src_loc)/history/history_inner_widget.h
|
<(src_loc)/history/history_inner_widget.h
|
||||||
<(src_loc)/history/history_location_manager.cpp
|
<(src_loc)/history/history_location_manager.cpp
|
||||||
|
|
Loading…
Add table
Reference in a new issue