2018-01-11 19:33:26 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
|
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
|
|
*/
|
|
|
|
#include "history/view/history_view_element.h"
|
|
|
|
|
2018-01-23 11:47:38 +00:00
|
|
|
#include "history/view/history_view_service_message.h"
|
2019-05-14 09:50:44 +00:00
|
|
|
#include "history/view/history_view_message.h"
|
2018-01-11 19:33:26 +00:00
|
|
|
#include "history/history_item_components.h"
|
2018-01-14 16:02:25 +00:00
|
|
|
#include "history/history_item.h"
|
2019-08-02 13:21:09 +00:00
|
|
|
#include "history/view/media/history_view_media.h"
|
|
|
|
#include "history/view/media/history_view_media_grouped.h"
|
|
|
|
#include "history/view/media/history_view_sticker.h"
|
2019-08-02 18:19:14 +00:00
|
|
|
#include "history/view/media/history_view_large_emoji.h"
|
2018-01-13 12:45:11 +00:00
|
|
|
#include "history/history.h"
|
2019-08-01 14:13:02 +00:00
|
|
|
#include "main/main_session.h"
|
|
|
|
#include "chat_helpers/stickers_emoji_pack.h"
|
2018-01-11 19:33:26 +00:00
|
|
|
#include "data/data_session.h"
|
2018-01-17 16:21:01 +00:00
|
|
|
#include "data/data_groups.h"
|
2018-01-14 16:02:25 +00:00
|
|
|
#include "data/data_media_types.h"
|
2018-01-21 19:21:08 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2018-01-14 16:02:25 +00:00
|
|
|
#include "layout.h"
|
2019-09-13 06:06:02 +00:00
|
|
|
#include "facades.h"
|
|
|
|
#include "app.h"
|
2018-01-21 19:21:08 +00:00
|
|
|
#include "styles/style_history.h"
|
2018-01-11 19:33:26 +00:00
|
|
|
|
|
|
|
namespace HistoryView {
|
2018-01-13 12:45:11 +00:00
|
|
|
namespace {
|
2018-01-11 19:33:26 +00:00
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
// A new message from the same sender is attached to previous within 15 minutes.
|
|
|
|
constexpr int kAttachMessageToPreviousSecondsDelta = 900;
|
|
|
|
|
2019-03-15 15:15:56 +00:00
|
|
|
bool IsAttachedToPreviousInSavedMessages(
|
|
|
|
not_null<HistoryItem*> previous,
|
|
|
|
not_null<HistoryItem*> item) {
|
|
|
|
const auto forwarded = previous->Has<HistoryMessageForwarded>();
|
|
|
|
const auto sender = previous->senderOriginal();
|
|
|
|
if (forwarded != item->Has<HistoryMessageForwarded>()) {
|
|
|
|
return false;
|
|
|
|
} else if (sender != item->senderOriginal()) {
|
|
|
|
return false;
|
|
|
|
} else if (!forwarded || sender) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const auto previousInfo = previous->hiddenForwardedInfo();
|
|
|
|
const auto itemInfo = item->hiddenForwardedInfo();
|
|
|
|
Assert(previousInfo != nullptr);
|
|
|
|
Assert(itemInfo != nullptr);
|
|
|
|
return (*previousInfo == *itemInfo);
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
} // namespace
|
2018-01-14 16:02:25 +00:00
|
|
|
|
2019-05-14 09:50:44 +00:00
|
|
|
|
|
|
|
std::unique_ptr<HistoryView::Element> SimpleElementDelegate::elementCreate(
|
|
|
|
not_null<HistoryMessage*> message) {
|
|
|
|
return std::make_unique<HistoryView::Message>(this, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<HistoryView::Element> SimpleElementDelegate::elementCreate(
|
|
|
|
not_null<HistoryService*> message) {
|
|
|
|
return std::make_unique<HistoryView::Service>(this, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleElementDelegate::elementUnderCursor(
|
|
|
|
not_null<const Element*> view) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleElementDelegate::elementAnimationAutoplayAsync(
|
|
|
|
not_null<const Element*> element) {
|
|
|
|
}
|
|
|
|
|
|
|
|
crl::time SimpleElementDelegate::elementHighlightTime(
|
|
|
|
not_null<const Element*> element) {
|
|
|
|
return crl::time(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleElementDelegate::elementInSelectionMode() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleElementDelegate::elementIntersectsRange(
|
|
|
|
not_null<const Element*> view,
|
|
|
|
int from,
|
|
|
|
int till) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-08-01 12:55:14 +00:00
|
|
|
void SimpleElementDelegate::elementStartStickerLoop(
|
2019-08-01 11:42:24 +00:00
|
|
|
not_null<const Element*> view) {
|
|
|
|
}
|
|
|
|
|
2020-01-14 11:55:18 +00:00
|
|
|
void SimpleElementDelegate::elementShowPollResults(
|
|
|
|
not_null<PollData*> poll,
|
|
|
|
FullMsgId context) {
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
TextSelection UnshiftItemSelection(
|
|
|
|
TextSelection selection,
|
|
|
|
uint16 byLength) {
|
|
|
|
return (selection == FullSelection)
|
|
|
|
? selection
|
|
|
|
: ::unshiftSelection(selection, byLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
TextSelection ShiftItemSelection(
|
|
|
|
TextSelection selection,
|
|
|
|
uint16 byLength) {
|
|
|
|
return (selection == FullSelection)
|
|
|
|
? selection
|
|
|
|
: ::shiftSelection(selection, byLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
TextSelection UnshiftItemSelection(
|
|
|
|
TextSelection selection,
|
2019-06-12 13:26:04 +00:00
|
|
|
const Ui::Text::String &byText) {
|
2018-01-14 16:02:25 +00:00
|
|
|
return UnshiftItemSelection(selection, byText.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
TextSelection ShiftItemSelection(
|
|
|
|
TextSelection selection,
|
2019-06-12 13:26:04 +00:00
|
|
|
const Ui::Text::String &byText) {
|
2018-01-14 16:02:25 +00:00
|
|
|
return ShiftItemSelection(selection, byText.length());
|
|
|
|
}
|
|
|
|
|
2020-02-20 14:16:42 +00:00
|
|
|
void UnreadBar::init() {
|
|
|
|
text = tr::lng_unread_bar_some(tr::now);
|
2018-01-21 19:21:08 +00:00
|
|
|
width = st::semiboldFont->width(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
int UnreadBar::height() {
|
|
|
|
return st::historyUnreadBarHeight + st::historyUnreadBarMargin;
|
|
|
|
}
|
|
|
|
|
|
|
|
int UnreadBar::marginTop() {
|
|
|
|
return st::lineWidth + st::historyUnreadBarMargin;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UnreadBar::paint(Painter &p, int y, int w) const {
|
|
|
|
const auto bottom = y + height();
|
|
|
|
y += marginTop();
|
|
|
|
p.fillRect(
|
|
|
|
0,
|
|
|
|
y,
|
|
|
|
w,
|
|
|
|
height() - marginTop() - st::lineWidth,
|
|
|
|
st::historyUnreadBarBg);
|
|
|
|
p.fillRect(
|
|
|
|
0,
|
|
|
|
bottom - 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,
|
|
|
|
st::msgMaxWidth
|
|
|
|
+ 2 * st::msgPhotoSkip
|
|
|
|
+ 2 * st::msgMargin.left());
|
|
|
|
}
|
|
|
|
w = maxwidth;
|
|
|
|
|
|
|
|
const auto skip = st::historyUnreadBarHeight
|
|
|
|
- 2 * st::lineWidth
|
|
|
|
- st::historyUnreadBarFont->height;
|
|
|
|
p.drawText(
|
|
|
|
(w - width) / 2,
|
|
|
|
y + (skip / 2) + st::historyUnreadBarFont->ascent,
|
|
|
|
text);
|
|
|
|
}
|
|
|
|
|
2018-01-23 11:47:38 +00:00
|
|
|
|
2020-01-14 22:23:16 +00:00
|
|
|
void DateBadge::init(const QString &date) {
|
|
|
|
text = date;
|
2018-01-23 11:47:38 +00:00
|
|
|
width = st::msgServiceFont->width(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
int DateBadge::height() const {
|
|
|
|
return st::msgServiceMargin.top()
|
|
|
|
+ st::msgServicePadding.top()
|
|
|
|
+ st::msgServiceFont->height
|
|
|
|
+ st::msgServicePadding.bottom()
|
|
|
|
+ st::msgServiceMargin.bottom();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DateBadge::paint(Painter &p, int y, int w) const {
|
|
|
|
ServiceMessagePainter::paintDate(p, text, width, y, w);
|
|
|
|
}
|
|
|
|
|
2018-01-19 17:10:58 +00:00
|
|
|
Element::Element(
|
|
|
|
not_null<ElementDelegate*> delegate,
|
|
|
|
not_null<HistoryItem*> data)
|
|
|
|
: _delegate(delegate)
|
|
|
|
, _data(data)
|
2020-01-14 22:23:16 +00:00
|
|
|
, _isScheduledUntilOnline(IsItemScheduledUntilOnline(data))
|
|
|
|
, _dateTime(_isScheduledUntilOnline ? QDateTime() : ItemDateTime(data))
|
2018-01-19 17:10:58 +00:00
|
|
|
, _context(delegate->elementContext()) {
|
2019-01-03 12:36:01 +00:00
|
|
|
history()->owner().registerItemView(this);
|
2018-01-17 16:21:01 +00:00
|
|
|
refreshMedia();
|
2018-01-30 10:51:35 +00:00
|
|
|
if (_context == Context::History) {
|
2019-01-03 12:36:01 +00:00
|
|
|
history()->setHasPendingResizedItems();
|
2018-01-30 10:51:35 +00:00
|
|
|
}
|
2018-01-11 19:33:26 +00:00
|
|
|
}
|
|
|
|
|
2018-01-19 17:10:58 +00:00
|
|
|
not_null<ElementDelegate*> Element::delegate() const {
|
|
|
|
return _delegate;
|
|
|
|
}
|
|
|
|
|
2018-01-11 19:33:26 +00:00
|
|
|
not_null<HistoryItem*> Element::data() const {
|
|
|
|
return _data;
|
|
|
|
}
|
|
|
|
|
2019-01-03 12:36:01 +00:00
|
|
|
not_null<History*> Element::history() const {
|
|
|
|
return _data->history();
|
|
|
|
}
|
|
|
|
|
2018-02-03 19:52:35 +00:00
|
|
|
QDateTime Element::dateTime() const {
|
|
|
|
return _dateTime;
|
|
|
|
}
|
|
|
|
|
2019-08-02 13:21:09 +00:00
|
|
|
Media *Element::media() const {
|
2018-01-14 16:02:25 +00:00
|
|
|
return _media.get();
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
Context Element::context() const {
|
|
|
|
return _context;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Element::y() const {
|
|
|
|
return _y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Element::setY(int y) {
|
|
|
|
_y = y;
|
|
|
|
}
|
|
|
|
|
2018-06-28 15:20:05 +00:00
|
|
|
void Element::refreshDataIdHook() {
|
|
|
|
}
|
|
|
|
|
2018-02-16 17:59:35 +00:00
|
|
|
void Element::paintHighlight(
|
|
|
|
Painter &p,
|
|
|
|
int geometryHeight) const {
|
|
|
|
const auto animms = delegate()->elementHighlightTime(this);
|
|
|
|
if (!animms
|
|
|
|
|| animms >= st::activeFadeInDuration + st::activeFadeOutDuration) {
|
|
|
|
return;
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
|
2018-02-16 17:59:35 +00:00
|
|
|
const auto top = marginTop();
|
|
|
|
const auto bottom = marginBottom();
|
|
|
|
const auto fill = qMin(top, bottom);
|
|
|
|
const auto skiptop = top - fill;
|
|
|
|
const auto fillheight = fill + geometryHeight + fill;
|
|
|
|
|
|
|
|
const auto dt = (animms > st::activeFadeInDuration)
|
|
|
|
? (1. - (animms - st::activeFadeInDuration)
|
|
|
|
/ float64(st::activeFadeOutDuration))
|
|
|
|
: (animms / float64(st::activeFadeInDuration));
|
|
|
|
const auto o = p.opacity();
|
|
|
|
p.setOpacity(o * dt);
|
|
|
|
p.fillRect(
|
|
|
|
0,
|
|
|
|
skiptop,
|
|
|
|
width(),
|
|
|
|
fillheight,
|
|
|
|
st::defaultTextPalette.selectOverlay);
|
|
|
|
p.setOpacity(o);
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::isUnderCursor() const {
|
2018-01-26 15:40:11 +00:00
|
|
|
return _delegate->elementUnderCursor(this);
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
|
2020-02-26 11:25:51 +00:00
|
|
|
bool Element::isLastAndSelfMessage() const {
|
|
|
|
if (!hasOutLayout()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (const auto last = data()->_history->lastMessage()) {
|
|
|
|
return last == data();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
void Element::setPendingResize() {
|
|
|
|
_flags |= Flag::NeedsResize;
|
|
|
|
if (_context == Context::History) {
|
|
|
|
data()->_history->setHasPendingResizedItems();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::pendingResize() const {
|
|
|
|
return _flags & Flag::NeedsResize;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::isAttachedToPrevious() const {
|
|
|
|
return _flags & Flag::AttachedToPrevious;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::isAttachedToNext() const {
|
|
|
|
return _flags & Flag::AttachedToNext;
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
int Element::skipBlockWidth() const {
|
|
|
|
return st::msgDateSpace + infoWidth() - st::msgDateDelta.x();
|
|
|
|
}
|
|
|
|
|
|
|
|
int Element::skipBlockHeight() const {
|
|
|
|
return st::msgDateFont->height - st::msgDateDelta.y();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Element::skipBlock() const {
|
|
|
|
return textcmdSkipBlock(skipBlockWidth(), skipBlockHeight());
|
|
|
|
}
|
|
|
|
|
|
|
|
int Element::infoWidth() const {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::isHiddenByGroup() const {
|
|
|
|
return _flags & Flag::HiddenByGroup;
|
|
|
|
}
|
2018-01-17 16:21:01 +00:00
|
|
|
|
2018-01-30 13:17:50 +00:00
|
|
|
bool Element::isHidden() const {
|
|
|
|
return isHiddenByGroup();
|
|
|
|
}
|
|
|
|
|
2018-01-17 16:21:01 +00:00
|
|
|
void Element::refreshMedia() {
|
|
|
|
_flags &= ~Flag::HiddenByGroup;
|
2018-01-14 16:02:25 +00:00
|
|
|
|
2018-01-17 16:21:01 +00:00
|
|
|
const auto item = data();
|
|
|
|
const auto media = item->media();
|
|
|
|
if (media && media->canBeGrouped()) {
|
2019-01-03 12:36:01 +00:00
|
|
|
if (const auto group = history()->owner().groups().find(item)) {
|
2018-01-17 16:21:01 +00:00
|
|
|
if (group->items.back() != item) {
|
|
|
|
_media = nullptr;
|
|
|
|
_flags |= Flag::HiddenByGroup;
|
|
|
|
} else {
|
2019-08-02 13:21:09 +00:00
|
|
|
_media = std::make_unique<GroupedMedia>(
|
2018-01-17 16:21:01 +00:00
|
|
|
this,
|
|
|
|
group->items);
|
2018-01-21 09:05:30 +00:00
|
|
|
if (!pendingResize()) {
|
2019-01-03 12:36:01 +00:00
|
|
|
history()->owner().requestViewResize(this);
|
2018-01-21 09:05:30 +00:00
|
|
|
}
|
2018-01-17 16:21:01 +00:00
|
|
|
}
|
|
|
|
return;
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-02 18:19:14 +00:00
|
|
|
const auto session = &history()->session();
|
|
|
|
if (const auto media = _data->media()) {
|
|
|
|
_media = media->createView(this);
|
|
|
|
} else if (_data->isIsolatedEmoji()
|
|
|
|
&& session->settings().largeEmoji()) {
|
|
|
|
const auto emoji = _data->isolatedEmoji();
|
|
|
|
const auto emojiStickers = &session->emojiStickersPack();
|
2019-08-07 14:22:51 +00:00
|
|
|
if (const auto sticker = emojiStickers->stickerForEmoji(emoji)) {
|
2019-08-02 18:19:14 +00:00
|
|
|
_media = std::make_unique<UnwrappedMedia>(
|
|
|
|
this,
|
2019-08-07 14:22:51 +00:00
|
|
|
std::make_unique<Sticker>(
|
|
|
|
this,
|
|
|
|
sticker.document,
|
|
|
|
sticker.replacements));
|
2019-08-02 18:19:14 +00:00
|
|
|
} else {
|
|
|
|
_media = std::make_unique<UnwrappedMedia>(
|
|
|
|
this,
|
|
|
|
std::make_unique<LargeEmoji>(this, emoji));
|
|
|
|
}
|
2018-01-17 16:21:01 +00:00
|
|
|
} else {
|
|
|
|
_media = nullptr;
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
void Element::previousInBlocksChanged() {
|
|
|
|
recountDisplayDateInBlocks();
|
|
|
|
recountAttachToPreviousInBlocks();
|
|
|
|
}
|
|
|
|
|
2018-01-19 17:10:58 +00:00
|
|
|
void Element::nextInBlocksRemoved() {
|
2018-01-13 12:45:11 +00:00
|
|
|
setAttachToNext(false);
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
void Element::refreshDataId() {
|
|
|
|
if (const auto media = this->media()) {
|
2018-01-17 16:21:01 +00:00
|
|
|
media->refreshParentId(data());
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
2018-06-28 15:20:05 +00:00
|
|
|
refreshDataIdHook();
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
|
2019-05-24 07:55:16 +00:00
|
|
|
const auto mayBeAttached = [](not_null<HistoryItem*> item) {
|
|
|
|
return !item->serviceMsg()
|
|
|
|
&& !item->isEmpty()
|
|
|
|
&& !item->isPost()
|
|
|
|
&& (item->from() != item->history()->peer
|
|
|
|
|| !item->from()->isChannel());
|
|
|
|
};
|
2018-01-13 12:45:11 +00:00
|
|
|
const auto item = data();
|
2018-01-23 11:47:38 +00:00
|
|
|
if (!Has<DateBadge>() && !Has<UnreadBar>()) {
|
2018-01-13 12:45:11 +00:00
|
|
|
const auto prev = previous->data();
|
2019-05-24 07:55:16 +00:00
|
|
|
const auto possible = (std::abs(prev->date() - item->date())
|
2019-04-15 11:54:03 +00:00
|
|
|
< kAttachMessageToPreviousSecondsDelta)
|
2019-05-24 07:55:16 +00:00
|
|
|
&& mayBeAttached(item)
|
|
|
|
&& mayBeAttached(prev);
|
2018-01-13 12:45:11 +00:00
|
|
|
if (possible) {
|
|
|
|
if (item->history()->peer->isSelf()) {
|
2019-03-15 15:15:56 +00:00
|
|
|
return IsAttachedToPreviousInSavedMessages(prev, item);
|
2018-01-13 12:45:11 +00:00
|
|
|
} else {
|
|
|
|
return prev->from() == item->from();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-02-20 14:16:42 +00:00
|
|
|
void Element::createUnreadBar() {
|
|
|
|
if (!AddComponents(UnreadBar::Bit())) {
|
2018-01-21 19:21:08 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-02-20 14:16:42 +00:00
|
|
|
const auto bar = Get<UnreadBar>();
|
|
|
|
bar->init();
|
2018-01-21 19:21:08 +00:00
|
|
|
if (data()->mainView() == this) {
|
|
|
|
recountAttachToPreviousInBlocks();
|
|
|
|
}
|
2020-02-20 14:16:42 +00:00
|
|
|
history()->owner().requestViewResize(this);
|
2018-01-21 19:21:08 +00:00
|
|
|
}
|
|
|
|
|
2020-02-20 14:16:42 +00:00
|
|
|
void Element::destroyUnreadBar() {
|
|
|
|
if (!Has<UnreadBar>()) {
|
2018-01-21 19:21:08 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-02-20 14:16:42 +00:00
|
|
|
RemoveComponents(UnreadBar::Bit());
|
|
|
|
history()->owner().requestViewResize(this);
|
|
|
|
if (data()->mainView() == this) {
|
|
|
|
recountAttachToPreviousInBlocks();
|
2018-01-21 19:21:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-23 11:47:38 +00:00
|
|
|
int Element::displayedDateHeight() const {
|
|
|
|
if (auto date = Get<DateBadge>()) {
|
|
|
|
return date->height();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::displayDate() const {
|
|
|
|
return Has<DateBadge>();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::isInOneDayWithPrevious() const {
|
|
|
|
return !data()->isEmpty() && !displayDate();
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
void Element::recountAttachToPreviousInBlocks() {
|
|
|
|
auto attachToPrevious = false;
|
|
|
|
if (const auto previous = previousInBlocks()) {
|
|
|
|
attachToPrevious = computeIsAttachToPrevious(previous);
|
|
|
|
previous->setAttachToNext(attachToPrevious);
|
|
|
|
}
|
|
|
|
setAttachToPrevious(attachToPrevious);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Element::recountDisplayDateInBlocks() {
|
|
|
|
setDisplayDate([&] {
|
|
|
|
const auto item = data();
|
|
|
|
if (item->isEmpty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-02-03 19:52:35 +00:00
|
|
|
if (const auto previous = previousInBlocks()) {
|
2018-01-13 12:45:11 +00:00
|
|
|
const auto prev = previous->data();
|
2018-02-03 19:52:35 +00:00
|
|
|
return prev->isEmpty()
|
|
|
|
|| (previous->dateTime().date() != dateTime().date());
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}());
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize Element::countOptimalSize() {
|
|
|
|
return performCountOptimalSize();
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize Element::countCurrentSize(int newWidth) {
|
|
|
|
if (_flags & Flag::NeedsResize) {
|
|
|
|
_flags &= ~Flag::NeedsResize;
|
|
|
|
initDimensions();
|
|
|
|
}
|
|
|
|
return performCountCurrentSize(newWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Element::setDisplayDate(bool displayDate) {
|
|
|
|
const auto item = data();
|
2018-01-23 11:47:38 +00:00
|
|
|
if (displayDate && !Has<DateBadge>()) {
|
|
|
|
AddComponents(DateBadge::Bit());
|
2020-01-14 22:23:16 +00:00
|
|
|
Get<DateBadge>()->init(ItemDateText(item, _isScheduledUntilOnline));
|
2018-01-13 12:45:11 +00:00
|
|
|
setPendingResize();
|
2018-01-23 11:47:38 +00:00
|
|
|
} else if (!displayDate && Has<DateBadge>()) {
|
|
|
|
RemoveComponents(DateBadge::Bit());
|
2018-01-13 12:45:11 +00:00
|
|
|
setPendingResize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Element::setAttachToNext(bool attachToNext) {
|
|
|
|
if (attachToNext && !(_flags & Flag::AttachedToNext)) {
|
|
|
|
_flags |= Flag::AttachedToNext;
|
2018-01-19 17:10:58 +00:00
|
|
|
setPendingResize();
|
2018-01-13 12:45:11 +00:00
|
|
|
} else if (!attachToNext && (_flags & Flag::AttachedToNext)) {
|
|
|
|
_flags &= ~Flag::AttachedToNext;
|
2018-01-19 17:10:58 +00:00
|
|
|
setPendingResize();
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Element::setAttachToPrevious(bool attachToPrevious) {
|
|
|
|
if (attachToPrevious && !(_flags & Flag::AttachedToPrevious)) {
|
|
|
|
_flags |= Flag::AttachedToPrevious;
|
2018-01-19 17:10:58 +00:00
|
|
|
setPendingResize();
|
2018-01-13 12:45:11 +00:00
|
|
|
} else if (!attachToPrevious && (_flags & Flag::AttachedToPrevious)) {
|
|
|
|
_flags &= ~Flag::AttachedToPrevious;
|
2018-01-19 17:10:58 +00:00
|
|
|
setPendingResize();
|
2018-01-13 12:45:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::displayFromPhoto() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::hasFromPhoto() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::hasFromName() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::displayFromName() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
bool Element::displayForwardedFrom() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::hasOutLayout() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::drawBubble() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::hasBubble() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::hasFastReply() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::displayFastReply() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::displayRightAction() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Element::drawRightAction(
|
|
|
|
Painter &p,
|
|
|
|
int left,
|
|
|
|
int top,
|
|
|
|
int outerWidth) const {
|
|
|
|
}
|
|
|
|
|
|
|
|
ClickHandlerPtr Element::rightActionLink() const {
|
|
|
|
return ClickHandlerPtr();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::displayEditedBadge() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-02-03 19:52:35 +00:00
|
|
|
TimeId Element::displayedEditDate() const {
|
|
|
|
return TimeId(0);
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
|
|
|
|
2018-01-18 13:59:22 +00:00
|
|
|
bool Element::hasVisibleText() const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-01-24 09:50:20 +00:00
|
|
|
auto Element::verticalRepaintRange() const -> VerticalRepaintRange {
|
|
|
|
return {
|
|
|
|
.top = 0,
|
|
|
|
.height = height()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-05-14 09:50:44 +00:00
|
|
|
void Element::unloadHeavyPart() {
|
|
|
|
if (_media) {
|
|
|
|
_media->unloadHeavyPart();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
HistoryBlock *Element::block() {
|
|
|
|
return _block;
|
|
|
|
}
|
|
|
|
|
|
|
|
const HistoryBlock *Element::block() const {
|
|
|
|
return _block;
|
|
|
|
}
|
|
|
|
|
2018-01-11 19:33:26 +00:00
|
|
|
void Element::attachToBlock(not_null<HistoryBlock*> block, int index) {
|
2019-08-08 14:13:26 +00:00
|
|
|
Expects(_data->isHistoryEntry());
|
2018-01-11 19:33:26 +00:00
|
|
|
Expects(_block == nullptr);
|
|
|
|
Expects(_indexInBlock < 0);
|
|
|
|
Expects(index >= 0);
|
|
|
|
|
|
|
|
_block = block;
|
|
|
|
_indexInBlock = index;
|
|
|
|
_data->setMainView(this);
|
2018-01-19 12:13:06 +00:00
|
|
|
previousInBlocksChanged();
|
2018-01-11 19:33:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Element::removeFromBlock() {
|
|
|
|
Expects(_block != nullptr);
|
|
|
|
|
|
|
|
_block->remove(this);
|
|
|
|
}
|
|
|
|
|
2018-01-18 13:59:22 +00:00
|
|
|
void Element::refreshInBlock() {
|
|
|
|
Expects(_block != nullptr);
|
|
|
|
|
|
|
|
_block->refreshView(this);
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
void Element::setIndexInBlock(int index) {
|
|
|
|
Expects(_block != nullptr);
|
|
|
|
Expects(index >= 0);
|
|
|
|
|
|
|
|
_indexInBlock = index;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Element::indexInBlock() const {
|
|
|
|
Expects((_indexInBlock >= 0) == (_block != nullptr));
|
|
|
|
Expects((_block == nullptr) || (_block->messages[_indexInBlock].get() == this));
|
|
|
|
|
|
|
|
return _indexInBlock;
|
|
|
|
}
|
|
|
|
|
2018-01-11 19:33:26 +00:00
|
|
|
Element *Element::previousInBlocks() const {
|
|
|
|
if (_block && _indexInBlock >= 0) {
|
|
|
|
if (_indexInBlock > 0) {
|
|
|
|
return _block->messages[_indexInBlock - 1].get();
|
|
|
|
}
|
|
|
|
if (auto previous = _block->previousBlock()) {
|
|
|
|
Assert(!previous->messages.empty());
|
|
|
|
return previous->messages.back().get();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Element *Element::nextInBlocks() const {
|
|
|
|
if (_block && _indexInBlock >= 0) {
|
|
|
|
if (_indexInBlock + 1 < _block->messages.size()) {
|
|
|
|
return _block->messages[_indexInBlock + 1].get();
|
|
|
|
}
|
|
|
|
if (auto next = _block->nextBlock()) {
|
|
|
|
Assert(!next->messages.empty());
|
|
|
|
return next->messages.front().get();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
void Element::drawInfo(
|
|
|
|
Painter &p,
|
|
|
|
int right,
|
|
|
|
int bottom,
|
|
|
|
int width,
|
|
|
|
bool selected,
|
|
|
|
InfoDisplayType type) const {
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Element::pointInTime(
|
|
|
|
int right,
|
|
|
|
int bottom,
|
|
|
|
QPoint point,
|
|
|
|
InfoDisplayType type) const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
TextSelection Element::adjustSelection(
|
|
|
|
TextSelection selection,
|
|
|
|
TextSelectType type) const {
|
|
|
|
return selection;
|
|
|
|
}
|
|
|
|
|
2018-01-11 19:33:26 +00:00
|
|
|
void Element::clickHandlerActiveChanged(
|
|
|
|
const ClickHandlerPtr &handler,
|
|
|
|
bool active) {
|
|
|
|
if (const auto markup = _data->Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
if (const auto keyboard = markup->inlineKeyboard.get()) {
|
|
|
|
keyboard->clickHandlerActiveChanged(handler, active);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
App::hoveredLinkItem(active ? this : nullptr);
|
2019-01-03 12:36:01 +00:00
|
|
|
history()->owner().requestViewRepaint(this);
|
2018-01-14 16:02:25 +00:00
|
|
|
if (const auto media = this->media()) {
|
2018-01-11 19:33:26 +00:00
|
|
|
media->clickHandlerActiveChanged(handler, active);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Element::clickHandlerPressedChanged(
|
|
|
|
const ClickHandlerPtr &handler,
|
|
|
|
bool pressed) {
|
|
|
|
if (const auto markup = _data->Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
if (const auto keyboard = markup->inlineKeyboard.get()) {
|
|
|
|
keyboard->clickHandlerPressedChanged(handler, pressed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
App::pressedLinkItem(pressed ? this : nullptr);
|
2019-01-03 12:36:01 +00:00
|
|
|
history()->owner().requestViewRepaint(this);
|
2018-01-14 16:02:25 +00:00
|
|
|
if (const auto media = this->media()) {
|
2018-01-11 19:33:26 +00:00
|
|
|
media->clickHandlerPressedChanged(handler, pressed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Element::~Element() {
|
2018-01-31 17:10:29 +00:00
|
|
|
if (_data->mainView() == this) {
|
|
|
|
_data->clearMainView();
|
|
|
|
}
|
2018-03-10 12:47:19 +00:00
|
|
|
if (_context == Context::History) {
|
2019-01-03 12:36:01 +00:00
|
|
|
history()->owner().notifyViewRemoved(this);
|
2018-03-10 12:47:19 +00:00
|
|
|
}
|
2019-01-03 12:36:01 +00:00
|
|
|
history()->owner().unregisterItemView(this);
|
2018-01-11 19:33:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace HistoryView
|