Start HistoryView::Message class for item view.

This commit is contained in:
John Preston 2018-01-10 16:13:33 +03:00
parent 794e31505b
commit 8060cb7426
22 changed files with 696 additions and 514 deletions

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/admin_log/history_admin_log_section.h" #include "history/admin_log/history_admin_log_section.h"
#include "history/admin_log/history_admin_log_filter.h" #include "history/admin_log/history_admin_log_filter.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_message.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"
@ -54,16 +55,16 @@ void InnerWidget::enumerateItems(Method method) {
auto begin = std::rbegin(_items), end = std::rend(_items); auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = TopToBottom ? std::lower_bound(begin, end, _visibleTop, [this](auto &elem, int top) { auto from = TopToBottom ? std::lower_bound(begin, end, _visibleTop, [this](auto &elem, int top) {
return this->itemTop(elem) + elem->height() <= top; return this->itemTop(elem) + elem->data()->height() <= top;
}) : std::upper_bound(begin, end, _visibleBottom, [this](int bottom, auto &elem) { }) : std::upper_bound(begin, end, _visibleBottom, [this](int bottom, auto &elem) {
return this->itemTop(elem) + elem->height() >= bottom; return this->itemTop(elem) + elem->data()->height() >= bottom;
}); });
auto wasEnd = (from == end); auto wasEnd = (from == end);
if (wasEnd) { if (wasEnd) {
--from; --from;
} }
if (TopToBottom) { if (TopToBottom) {
Assert(itemTop(from->get()) + from->get()->height() > _visibleTop); Assert(itemTop(from->get()) + from->get()->data()->height() > _visibleTop);
} else { } else {
Assert(itemTop(from->get()) < _visibleBottom); Assert(itemTop(from->get()) < _visibleBottom);
} }
@ -71,7 +72,7 @@ void InnerWidget::enumerateItems(Method method) {
while (true) { while (true) {
auto item = from->get(); auto item = from->get();
auto itemtop = itemTop(item); auto itemtop = itemTop(item);
auto itembottom = itemtop + item->height(); auto itembottom = itemtop + item->data()->height();
// Binary search should've skipped all the items that are above / below the visible area. // Binary search should've skipped all the items that are above / below the visible area.
if (TopToBottom) { if (TopToBottom) {
@ -114,8 +115,9 @@ void InnerWidget::enumerateUserpics(Method method) {
// -1 means we didn't find an attached to next message yet. // -1 means we didn't find an attached to next message yet.
int lowestAttachedItemTop = -1; int lowestAttachedItemTop = -1;
auto userpicCallback = [this, &lowestAttachedItemTop, &method](HistoryItem *item, int itemtop, int itembottom) { auto userpicCallback = [&](Message *view, int itemtop, int itembottom) {
// Skip all service messages. // Skip all service messages.
const auto item = view->data();
auto message = item->toHistoryMessage(); auto message = item->toHistoryMessage();
if (!message) return true; if (!message) return true;
@ -160,7 +162,8 @@ void InnerWidget::enumerateDates(Method method) {
// -1 means we didn't find a same-day with previous message yet. // -1 means we didn't find a same-day with previous message yet.
auto lowestInOneDayItemBottom = -1; auto lowestInOneDayItemBottom = -1;
auto dateCallback = [this, &lowestInOneDayItemBottom, &method](HistoryItem *item, int itemtop, int itembottom) { auto dateCallback = [&](Message *view, int itemtop, int itembottom) {
const auto item = view->data();
if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) { if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) {
lowestInOneDayItemBottom = itembottom - item->marginBottom(); lowestInOneDayItemBottom = itembottom - item->marginBottom();
} }
@ -211,7 +214,7 @@ InnerWidget::InnerWidget(
Auth().data().itemRepaintRequest( Auth().data().itemRepaintRequest(
) | rpl::start_with_next([this](auto item) { ) | rpl::start_with_next([this](auto item) {
if (item->isLogEntry() && _history == item->history()) { if (item->isLogEntry() && _history == item->history()) {
repaintItem(item); repaintItem(viewForItem(item));
} }
}, lifetime()); }, lifetime());
subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); }); subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); });
@ -219,9 +222,11 @@ InnerWidget::InnerWidget(
if (_history != query.item->history() || !query.item->isLogEntry() || !isVisible()) { if (_history != query.item->history() || !query.item->isLogEntry() || !isVisible()) {
return; return;
} }
auto top = itemTop(query.item); if (const auto view = viewForItem(query.item)) {
if (top >= 0 && top + query.item->height() > _visibleTop && top < _visibleBottom) { auto top = itemTop(view);
*query.isVisible = true; if (top >= 0 && top + query.item->height() > _visibleTop && top < _visibleBottom) {
*query.isVisible = true;
}
} }
}); });
updateEmptyText(); updateEmptyText();
@ -252,7 +257,7 @@ void InnerWidget::updateVisibleTopItem() {
} else { } else {
auto begin = std::rbegin(_items), end = std::rend(_items); auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = std::lower_bound(begin, end, _visibleTop, [this](auto &&elem, int top) { auto from = std::lower_bound(begin, end, _visibleTop, [this](auto &&elem, int top) {
return this->itemTop(elem) + elem->height() <= top; return this->itemTop(elem) + elem->data()->height() <= top;
}); });
if (from != end) { if (from != end) {
_visibleTopItem = *from; _visibleTopItem = *from;
@ -516,7 +521,7 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
// When loading items up we just add them to the back of the _items vector. // When loading items up we just add them to the back of the _items vector.
// When loading items down we add them to a new vector and copy _items after them. // When loading items down we add them to a new vector and copy _items after them.
auto newItemsForDownDirection = std::vector<HistoryItemOwned>(); auto newItemsForDownDirection = std::vector<OwnedItem>();
auto oldItemsCount = _items.size(); auto oldItemsCount = _items.size();
auto &addToItems = (direction == Direction::Up) ? _items : newItemsForDownDirection; auto &addToItems = (direction == Direction::Up) ? _items : newItemsForDownDirection;
addToItems.reserve(oldItemsCount + events.size() * 2); addToItems.reserve(oldItemsCount + events.size() * 2);
@ -528,8 +533,9 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
} }
auto count = 0; auto count = 0;
GenerateItems(_history, _idManager, data, [this, id = data.vid.v, &addToItems, &count](HistoryItemOwned item) { GenerateItems(_history, _idManager, data, [this, id = data.vid.v, &addToItems, &count](OwnedItem item) {
_itemsByIds.emplace(id, item.get()); _itemsByIds.emplace(id, item.get());
_itemsByData.emplace(item->data(), item.get());
addToItems.push_back(std::move(item)); addToItems.push_back(std::move(item));
++count; ++count;
}); });
@ -575,9 +581,9 @@ void InnerWidget::itemsAdded(Direction direction, int addedCount) {
auto checkTo = (direction == Direction::Up) ? (_items.size() + 1) : (addedCount + 1); auto checkTo = (direction == Direction::Up) ? (_items.size() + 1) : (addedCount + 1);
for (auto i = checkFrom; i != checkTo; ++i) { for (auto i = checkFrom; i != checkTo; ++i) {
if (i > 0) { if (i > 0) {
auto item = _items[i - 1].get(); const auto item = _items[i - 1]->data();
if (i < _items.size()) { if (i < _items.size()) {
auto previous = _items[i].get(); const auto previous = _items[i]->data();
item->setLogEntryDisplayDate(item->date.date() != previous->date.date()); item->setLogEntryDisplayDate(item->date.date() != previous->date.date());
auto attachToPrevious = item->computeIsAttachToPrevious(previous); auto attachToPrevious = item->computeIsAttachToPrevious(previous);
item->setLogEntryAttachToPrevious(attachToPrevious); item->setLogEntryAttachToPrevious(attachToPrevious);
@ -603,7 +609,7 @@ int InnerWidget::resizeGetHeight(int newWidth) {
auto newHeight = 0; auto newHeight = 0;
for (auto &item : base::reversed(_items)) { for (auto &item : base::reversed(_items)) {
item->setY(newHeight); item->setY(newHeight);
newHeight += item->resizeGetHeight(newWidth); newHeight += item->data()->resizeGetHeight(newWidth);
} }
_itemsHeight = newHeight; _itemsHeight = newHeight;
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0; _itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0;
@ -611,7 +617,9 @@ int InnerWidget::resizeGetHeight(int newWidth) {
} }
void InnerWidget::restoreScrollPosition() { void InnerWidget::restoreScrollPosition() {
auto newVisibleTop = _visibleTopItem ? (itemTop(_visibleTopItem) + _visibleTopFromItem) : ScrollMax; auto newVisibleTop = _visibleTopItem
? (itemTop(_visibleTopItem) + _visibleTopFromItem)
: ScrollMax;
scrollToSignal.notify(newVisibleTop, true); scrollToSignal.notify(newVisibleTop, true);
} }
@ -630,7 +638,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
} else { } else {
auto begin = std::rbegin(_items), end = std::rend(_items); auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = std::lower_bound(begin, end, clip.top(), [this](auto &elem, int top) { auto from = std::lower_bound(begin, end, clip.top(), [this](auto &elem, int top) {
return this->itemTop(elem) + elem->height() <= top; return this->itemTop(elem) + elem->data()->height() <= top;
}); });
auto to = std::lower_bound(begin, end, clip.top() + clip.height(), [this](auto &elem, int bottom) { auto to = std::lower_bound(begin, end, clip.top() + clip.height(), [this](auto &elem, int bottom) {
return this->itemTop(elem) < bottom; return this->itemTop(elem) < bottom;
@ -639,15 +647,19 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
auto top = itemTop(from->get()); auto top = itemTop(from->get());
p.translate(0, top); p.translate(0, top);
for (auto i = from; i != to; ++i) { for (auto i = from; i != to; ++i) {
auto selection = (*i == _selectedItem) ? _selectedText : TextSelection(); const auto view = i->get();
(*i)->draw(p, clip.translated(0, -top), selection, ms); const auto selection = (view == _selectedItem)
auto height = (*i)->height(); ? _selectedText
: TextSelection();
const auto item = view->data();
item->draw(p, clip.translated(0, -top), selection, ms);
auto height = item->height();
top += height; top += height;
p.translate(0, height); p.translate(0, height);
} }
p.translate(0, -top); p.translate(0, -top);
enumerateUserpics([&p, &clip](not_null<HistoryMessage*> message, int userpicTop) { enumerateUserpics([&](not_null<HistoryMessage*> message, int userpicTop) {
// stop the enumeration if the userpic is below the painted rect // stop the enumeration if the userpic is below the painted rect
if (userpicTop >= clip.top() + clip.height()) { if (userpicTop >= clip.top() + clip.height()) {
return false; return false;
@ -662,7 +674,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.); auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.);
enumerateDates([&p, &clip, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](not_null<HistoryItem*> item, int itemtop, int dateTop) { enumerateDates([&](not_null<HistoryItem*> item, int itemtop, int dateTop) {
// stop the enumeration if the date is above the painted rect // stop the enumeration if the date is above the painted rect
if (dateTop + dateHeight <= clip.top()) { if (dateTop + dateHeight <= clip.top()) {
return false; return false;
@ -721,6 +733,16 @@ void InnerWidget::clearAfterFilterChange() {
updateSize(); updateSize();
} }
auto InnerWidget::viewForItem(const HistoryItem *item) -> Message* {
if (item) {
const auto i = _itemsByData.find(item);
if (i != _itemsByData.end()) {
return i->second;
}
}
return nullptr;
}
void InnerWidget::paintEmpty(Painter &p) { void InnerWidget::paintEmpty(Painter &p) {
style::font font(st::msgServiceFont); style::font font(st::msgServiceFont);
auto rectWidth = st::historyAdminLogEmptyWidth; auto rectWidth = st::historyAdminLogEmptyWidth;
@ -739,7 +761,9 @@ void InnerWidget::paintEmpty(Painter &p) {
} }
TextWithEntities InnerWidget::getSelectedText() const { TextWithEntities InnerWidget::getSelectedText() const {
return _selectedItem ? _selectedItem->selectedText(_selectedText) : TextWithEntities(); return _selectedItem
? _selectedItem->data()->selectedText(_selectedText)
: TextWithEntities();
} }
void InnerWidget::keyPressEvent(QKeyEvent *e) { void InnerWidget::keyPressEvent(QKeyEvent *e) {
@ -761,7 +785,7 @@ void InnerWidget::mouseDoubleClickEvent(QMouseEvent *e) {
if (((_mouseAction == MouseAction::Selecting && _selectedItem != nullptr) || (_mouseAction == MouseAction::None)) && _mouseSelectType == TextSelectType::Letters && _mouseActionItem) { if (((_mouseAction == MouseAction::Selecting && _selectedItem != nullptr) || (_mouseAction == MouseAction::None)) && _mouseSelectType == TextSelectType::Letters && _mouseActionItem) {
HistoryStateRequest request; HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol; request.flags |= Text::StateRequest::Flag::LookupSymbol;
auto dragState = _mouseActionItem->getState(_dragStartPosition, request); auto dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
if (dragState.cursor == HistoryInTextCursorState) { if (dragState.cursor == HistoryInTextCursorState) {
_mouseTextSymbol = dragState.symbol; _mouseTextSymbol = dragState.symbol;
_mouseSelectType = TextSelectType::Words; _mouseSelectType = TextSelectType::Words;
@ -802,7 +826,9 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
auto selTo = _selectedText.to; auto selTo = _selectedText.to;
hasSelected = (selTo > selFrom) ? 1 : 0; hasSelected = (selTo > selFrom) ? 1 : 0;
if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) { if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) {
auto mousePos = mapPointToItem(mapFromGlobal(_mousePosition), App::mousedItem()); auto mousePos = mapPointToItem(
mapFromGlobal(_mousePosition),
viewForItem(App::mousedItem()));
HistoryStateRequest request; HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol; request.flags |= Text::StateRequest::Flag::LookupSymbol;
auto dragState = App::mousedItem()->getState(mousePos, request); auto dragState = App::mousedItem()->getState(mousePos, request);
@ -1137,7 +1163,7 @@ void InnerWidget::enterEventHook(QEvent *e) {
void InnerWidget::leaveEventHook(QEvent *e) { void InnerWidget::leaveEventHook(QEvent *e) {
if (auto item = App::hoveredItem()) { if (auto item = App::hoveredItem()) {
repaintItem(item); repaintItem(viewForItem(item));
App::hoveredItem(nullptr); App::hoveredItem(nullptr);
} }
ClickHandler::clearActive(); ClickHandler::clearActive();
@ -1155,14 +1181,16 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
ClickHandler::pressed(); ClickHandler::pressed();
if (App::pressedItem() != App::hoveredItem()) { if (App::pressedItem() != App::hoveredItem()) {
repaintItem(App::pressedItem()); repaintItem(viewForItem(App::pressedItem()));
App::pressedItem(App::hoveredItem()); App::pressedItem(App::hoveredItem());
repaintItem(App::pressedItem()); repaintItem(viewForItem(App::pressedItem()));
} }
_mouseAction = MouseAction::None; _mouseAction = MouseAction::None;
_mouseActionItem = App::mousedItem(); _mouseActionItem = viewForItem(App::mousedItem());
_dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), _mouseActionItem); _dragStartPosition = mapPointToItem(
mapFromGlobal(screenPos),
_mouseActionItem);
_pressWasInactive = _controller->window()->wasInactivePress(); _pressWasInactive = _controller->window()->wasInactivePress();
if (_pressWasInactive) _controller->window()->setInactivePress(false); if (_pressWasInactive) _controller->window()->setInactivePress(false);
@ -1174,7 +1202,7 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) {
HistoryStateRequest request; HistoryStateRequest request;
request.flags = Text::StateRequest::Flag::LookupSymbol; request.flags = Text::StateRequest::Flag::LookupSymbol;
dragState = _mouseActionItem->getState(_dragStartPosition, request); dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
if (dragState.cursor == HistoryInTextCursorState) { if (dragState.cursor == HistoryInTextCursorState) {
auto selection = TextSelection { dragState.symbol, dragState.symbol }; auto selection = TextSelection { dragState.symbol, dragState.symbol };
repaintItem(std::exchange(_selectedItem, _mouseActionItem)); repaintItem(std::exchange(_selectedItem, _mouseActionItem));
@ -1188,7 +1216,7 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
} else if (App::pressedItem()) { } else if (App::pressedItem()) {
HistoryStateRequest request; HistoryStateRequest request;
request.flags = Text::StateRequest::Flag::LookupSymbol; request.flags = Text::StateRequest::Flag::LookupSymbol;
dragState = _mouseActionItem->getState(_dragStartPosition, request); dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
} }
if (_mouseSelectType != TextSelectType::Paragraphs) { if (_mouseSelectType != TextSelectType::Paragraphs) {
if (App::pressedItem()) { if (App::pressedItem()) {
@ -1243,7 +1271,7 @@ void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton but
activated = nullptr; activated = nullptr;
} }
if (App::pressedItem()) { if (App::pressedItem()) {
repaintItem(App::pressedItem()); repaintItem(viewForItem(App::pressedItem()));
App::pressedItem(nullptr); App::pressedItem(nullptr);
} }
@ -1284,30 +1312,31 @@ void InnerWidget::updateSelected() {
auto begin = std::rbegin(_items), end = std::rend(_items); auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = (point.y() >= _itemsTop && point.y() < _itemsTop + _itemsHeight) auto from = (point.y() >= _itemsTop && point.y() < _itemsTop + _itemsHeight)
? std::lower_bound(begin, end, point.y(), [this](auto &elem, int top) { ? std::lower_bound(begin, end, point.y(), [this](auto &elem, int top) {
return this->itemTop(elem) + elem->height() <= top; return this->itemTop(elem) + elem->data()->height() <= top;
}) })
: end; : end;
auto item = (from != end) ? from->get() : nullptr; const auto view = (from != end) ? from->get() : nullptr;
const auto item = view ? view->data().get() : nullptr;
if (item) { if (item) {
App::mousedItem(item); App::mousedItem(item);
itemPoint = mapPointToItem(point, item); itemPoint = mapPointToItem(point, view);
if (item->hasPoint(itemPoint)) { if (item->hasPoint(itemPoint)) {
if (App::hoveredItem() != item) { if (App::hoveredItem() != item) {
repaintItem(App::hoveredItem()); repaintItem(viewForItem(App::hoveredItem()));
App::hoveredItem(item); App::hoveredItem(item);
repaintItem(App::hoveredItem()); repaintItem(view);
} }
} else if (App::hoveredItem()) { } else if (App::hoveredItem()) {
repaintItem(App::hoveredItem()); repaintItem(viewForItem(App::hoveredItem()));
App::hoveredItem(nullptr); App::hoveredItem(nullptr);
} }
} }
HistoryTextState dragState; HistoryTextState dragState;
ClickHandlerHost *lnkhost = nullptr; ClickHandlerHost *lnkhost = nullptr;
auto selectingText = (item == _mouseActionItem && item == App::hoveredItem() && _selectedItem); auto selectingText = (view == _mouseActionItem && item == App::hoveredItem() && _selectedItem);
if (item) { if (view) {
if (item != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { if (view != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
if (_mouseAction == MouseAction::PrepareDrag) { if (_mouseAction == MouseAction::PrepareDrag) {
_mouseAction = MouseAction::Dragging; _mouseAction = MouseAction::Dragging;
InvokeQueued(this, [this] { performDrag(); }); InvokeQueued(this, [this] { performDrag(); });
@ -1369,7 +1398,9 @@ void InnerWidget::updateSelected() {
} }
auto selection = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }; auto selection = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) };
if (_mouseSelectType != TextSelectType::Letters) { if (_mouseSelectType != TextSelectType::Letters) {
selection = _mouseActionItem->adjustSelection(selection, _mouseSelectType); selection = _mouseActionItem->data()->adjustSelection(
selection,
_mouseSelectType);
} }
if (_selectedText != selection) { if (_selectedText != selection) {
_selectedText = selection; _selectedText = selection;
@ -1394,7 +1425,7 @@ void InnerWidget::updateSelected() {
if (auto pressedItem = App::pressedLinkItem()) { if (auto pressedItem = App::pressedLinkItem()) {
if (!pressedItem->detached()) { if (!pressedItem->detached()) {
if (pressedItem->history() == _history) { if (pressedItem->history() == _history) {
auto adjustedPoint = mapPointToItem(point, pressedItem); auto adjustedPoint = mapPointToItem(point, viewForItem(pressedItem));
pressedItem->updatePressed(adjustedPoint); pressedItem->updatePressed(adjustedPoint);
} }
} }
@ -1502,22 +1533,22 @@ void InnerWidget::performDrag() {
//} // TODO //} // TODO
} }
int InnerWidget::itemTop(not_null<const HistoryItem*> item) const { int InnerWidget::itemTop(not_null<const Message*> item) const {
return _itemsTop + item->y(); return _itemsTop + item->y();
} }
void InnerWidget::repaintItem(const HistoryItem *item) { void InnerWidget::repaintItem(const Message *item) {
if (!item) { if (!item) {
return; return;
} }
update(0, itemTop(item), width(), item->height()); update(0, itemTop(item), width(), item->data()->height());
} }
QPoint InnerWidget::mapPointToItem(QPoint point, const HistoryItem *item) const { QPoint InnerWidget::mapPointToItem(QPoint point, const Message *view) const {
if (!item) { if (!view) {
return QPoint(); return QPoint();
} }
return point - QPoint(0, itemTop(item)); return point - QPoint(0, itemTop(view));
} }
void InnerWidget::handlePendingHistoryResize() { void InnerWidget::handlePendingHistoryResize() {

View File

@ -22,6 +22,10 @@ namespace Window {
class Controller; class Controller;
} // namespace Window } // namespace Window
namespace HistoryView {
class Message;
} // namespace HistoryView
namespace AdminLog { namespace AdminLog {
class SectionMemento; class SectionMemento;
@ -86,6 +90,7 @@ protected:
int resizeGetHeight(int newWidth) override; int resizeGetHeight(int newWidth) override;
private: private:
using Message = HistoryView::Message;
enum class Direction { enum class Direction {
Up, Up,
Down, Down,
@ -107,9 +112,9 @@ private:
void mouseActionCancel(); void mouseActionCancel();
void updateSelected(); void updateSelected();
void performDrag(); void performDrag();
int itemTop(not_null<const HistoryItem*> item) const; int itemTop(not_null<const Message*> item) const;
void repaintItem(const HistoryItem *item); void repaintItem(const Message *item);
QPoint mapPointToItem(QPoint point, const HistoryItem *item) const; QPoint mapPointToItem(QPoint point, const Message *item) const;
void handlePendingHistoryResize(); void handlePendingHistoryResize();
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
@ -141,6 +146,7 @@ private:
void clearAfterFilterChange(); void clearAfterFilterChange();
void clearAndRequestLog(); void clearAndRequestLog();
void addEvents(Direction direction, const QVector<MTPChannelAdminLogEvent> &events); void addEvents(Direction direction, const QVector<MTPChannelAdminLogEvent> &events);
Message *viewForItem(const HistoryItem *item);
void toggleScrollDateShown(); void toggleScrollDateShown();
void repaintScrollDateCallback(); void repaintScrollDateCallback();
@ -152,7 +158,7 @@ private:
// This function finds all history items that are displayed and calls template method // This function finds all history items that are displayed and calls template method
// for each found message (in given direction) in the passed history with passed top offset. // for each found message (in given direction) in the passed history with passed top offset.
// //
// Method has "bool (*Method)(HistoryItem *item, int itemtop, int itembottom)" signature // Method has "bool (*Method)(Message *item, int itemtop, int itembottom)" signature
// if it returns false the enumeration stops immidiately. // if it returns false the enumeration stops immidiately.
template <EnumItemsDirection direction, typename Method> template <EnumItemsDirection direction, typename Method>
void enumerateItems(Method method); void enumerateItems(Method method);
@ -176,8 +182,9 @@ private:
not_null<Window::Controller*> _controller; not_null<Window::Controller*> _controller;
not_null<ChannelData*> _channel; not_null<ChannelData*> _channel;
not_null<History*> _history; not_null<History*> _history;
std::vector<HistoryItemOwned> _items; std::vector<OwnedItem> _items;
std::map<uint64, HistoryItem*> _itemsByIds; std::map<uint64, not_null<Message*>> _itemsByIds;
std::map<not_null<HistoryItem*>, not_null<Message*>, std::less<>> _itemsByData;
int _itemsTop = 0; int _itemsTop = 0;
int _itemsHeight = 0; int _itemsHeight = 0;
@ -185,14 +192,14 @@ private:
int _minHeight = 0; int _minHeight = 0;
int _visibleTop = 0; int _visibleTop = 0;
int _visibleBottom = 0; int _visibleBottom = 0;
HistoryItem *_visibleTopItem = nullptr; Message *_visibleTopItem = nullptr;
int _visibleTopFromItem = 0; int _visibleTopFromItem = 0;
bool _scrollDateShown = false; bool _scrollDateShown = false;
Animation _scrollDateOpacity; Animation _scrollDateOpacity;
SingleQueuedInvokation _scrollDateCheck; SingleQueuedInvokation _scrollDateCheck;
base::Timer _scrollDateHideTimer; base::Timer _scrollDateHideTimer;
HistoryItem *_scrollDateLastItem = nullptr; Message *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0; int _scrollDateLastItemTop = 0;
// Up - max, Down - min. // Up - max, Down - min.
@ -211,12 +218,12 @@ private:
TextSelectType _mouseSelectType = TextSelectType::Letters; TextSelectType _mouseSelectType = TextSelectType::Letters;
QPoint _dragStartPosition; QPoint _dragStartPosition;
QPoint _mousePosition; QPoint _mousePosition;
HistoryItem *_mouseActionItem = nullptr; Message *_mouseActionItem = nullptr;
HistoryCursorState _mouseCursorState = HistoryDefaultCursorState; HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
uint16 _mouseTextSymbol = 0; uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false; bool _pressWasInactive = false;
HistoryItem *_selectedItem = nullptr; Message *_selectedItem = nullptr;
TextSelection _selectedText; TextSelection _selectedText;
bool _wasSelectedText = false; // was some text selected in current drag action bool _wasSelectedText = false; // was some text selected in current drag action
Qt::CursorShape _cursor = style::cur_default; Qt::CursorShape _cursor = style::cur_default;

View File

@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "history/admin_log/history_admin_log_item.h" #include "history/admin_log/history_admin_log_item.h"
#include "history/admin_log/history_admin_log_inner.h"
#include "history/view/history_view_message.h"
#include "history/history_service.h" #include "history/history_service.h"
#include "history/history_message.h" #include "history/history_message.h"
#include "history/admin_log/history_admin_log_inner.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "core/tl_help.h" #include "core/tl_help.h"
@ -281,7 +282,32 @@ TextWithEntities GenerateParticipantChangeText(not_null<ChannelData*> channel, c
} // namespace } // namespace
void GenerateItems(not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event, base::lambda<void(HistoryItemOwned item)> callback) { OwnedItem::OwnedItem(not_null<HistoryItem*> data)
: _data(data)
, _view(std::make_unique<HistoryView::Message>(
data,
HistoryView::Context::AdminLog)) {
}
OwnedItem::OwnedItem(OwnedItem &&other)
: _data(base::take(other._data))
, _view(base::take(other._view)) {
}
OwnedItem &OwnedItem::operator=(OwnedItem &&other) {
_data = base::take(other._data);
_view = base::take(other._view);
return *this;
}
OwnedItem::~OwnedItem() {
_view = nullptr;
if (_data) {
_data->destroy();
}
}
void GenerateItems(not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event, base::lambda<void(OwnedItem item)> callback) {
Expects(history->peer->isChannel()); Expects(history->peer->isChannel());
auto id = event.vid.v; auto id = event.vid.v;
@ -290,7 +316,7 @@ void GenerateItems(not_null<History*> history, LocalIdManager &idManager, const
auto &action = event.vaction; auto &action = event.vaction;
auto date = event.vdate; auto date = event.vdate;
auto addPart = [&callback](not_null<HistoryItem*> item) { auto addPart = [&callback](not_null<HistoryItem*> item) {
return callback(HistoryItemOwned(item)); return callback(OwnedItem(item));
}; };
using Flag = MTPDmessage::Flag; using Flag = MTPDmessage::Flag;

View File

@ -9,42 +9,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace AdminLog { namespace AdminLog {
class HistoryItemOwned; class OwnedItem;
class LocalIdManager; class LocalIdManager;
void GenerateItems(not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event, base::lambda<void(HistoryItemOwned item)> callback); void GenerateItems(not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event, base::lambda<void(OwnedItem item)> callback);
// Smart pointer wrapper for HistoryItem* that destroys the owned item. // Smart pointer wrapper for HistoryItem* that destroys the owned item.
class HistoryItemOwned { class OwnedItem {
public: public:
explicit HistoryItemOwned(not_null<HistoryItem*> data) : _data(data) { explicit OwnedItem(not_null<HistoryItem*> data);
} OwnedItem(const OwnedItem &other) = delete;
HistoryItemOwned(const HistoryItemOwned &other) = delete; OwnedItem &operator=(const OwnedItem &other) = delete;
HistoryItemOwned &operator=(const HistoryItemOwned &other) = delete; OwnedItem(OwnedItem &&other);
HistoryItemOwned(HistoryItemOwned &&other) : _data(base::take(other._data)) { OwnedItem &operator=(OwnedItem &&other);
} ~OwnedItem();
HistoryItemOwned &operator=(HistoryItemOwned &&other) {
_data = base::take(other._data);
return *this;
}
~HistoryItemOwned() {
if (_data) {
_data->destroy();
}
}
HistoryItem *get() const { HistoryView::Message *get() const {
return _data; return _view.get();
} }
HistoryItem *operator->() const { HistoryView::Message *operator->() const {
return get(); return get();
} }
operator HistoryItem*() const { operator HistoryView::Message*() const {
return get(); return get();
} }
private: private:
HistoryItem *_data = nullptr; HistoryItem *_data = nullptr;
std::unique_ptr<HistoryView::Message> _view;
}; };

View File

@ -123,6 +123,8 @@ private:
class SectionMemento : public Window::SectionMemento { class SectionMemento : public Window::SectionMemento {
public: public:
using Message = HistoryView::Message;
SectionMemento(not_null<ChannelData*> channel) : _channel(channel) { SectionMemento(not_null<ChannelData*> channel) : _channel(channel) {
} }
@ -155,7 +157,11 @@ public:
return std::move(_adminsCanEdit); return std::move(_adminsCanEdit);
} }
void setItems(std::vector<HistoryItemOwned> &&items, std::map<uint64, HistoryItem*> &&itemsByIds, bool upLoaded, bool downLoaded) { void setItems(
std::vector<OwnedItem> &&items,
std::map<uint64, not_null<Message*>> &&itemsByIds,
bool upLoaded,
bool downLoaded) {
_items = std::move(items); _items = std::move(items);
_itemsByIds = std::move(itemsByIds); _itemsByIds = std::move(itemsByIds);
_upLoaded = upLoaded; _upLoaded = upLoaded;
@ -170,10 +176,10 @@ public:
void setIdManager(LocalIdManager &&manager) { void setIdManager(LocalIdManager &&manager) {
_idManager = std::move(manager); _idManager = std::move(manager);
} }
std::vector<HistoryItemOwned> takeItems() { std::vector<OwnedItem> takeItems() {
return std::move(_items); return std::move(_items);
} }
std::map<uint64, HistoryItem*> takeItemsByIds() { std::map<uint64, not_null<Message*>> takeItemsByIds() {
return std::move(_itemsByIds); return std::move(_itemsByIds);
} }
LocalIdManager takeIdManager() { LocalIdManager takeIdManager() {
@ -197,8 +203,8 @@ private:
int _scrollTop = 0; int _scrollTop = 0;
std::vector<not_null<UserData*>> _admins; std::vector<not_null<UserData*>> _admins;
std::vector<not_null<UserData*>> _adminsCanEdit; std::vector<not_null<UserData*>> _adminsCanEdit;
std::vector<HistoryItemOwned> _items; std::vector<OwnedItem> _items;
std::map<uint64, HistoryItem*> _itemsByIds; std::map<uint64, not_null<Message*>> _itemsByIds;
bool _upLoaded = false; bool _upLoaded = false;
bool _downLoaded = true; bool _downLoaded = true;
LocalIdManager _idManager; LocalIdManager _idManager;

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_top_bar_widget.h"
#include "history/view/history_view_list_widget.h" #include "history/view/history_view_list_widget.h"
#include "history/view/history_view_message.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
@ -135,6 +136,10 @@ bool Widget::cmd_search() {
return true; return true;
} }
HistoryView::Context Widget::listContext() {
return HistoryView::Context::Feed;
}
void Widget::listScrollTo(int top) { void Widget::listScrollTo(int top) {
if (_scroll->scrollTop() != top) { if (_scroll->scrollTop() != top) {
_scroll->scrollToY(top); _scroll->scrollToY(top);

View File

@ -59,6 +59,7 @@ public:
bool cmd_search() override; bool cmd_search() override;
// HistoryView::ListDelegate interface. // HistoryView::ListDelegate interface.
HistoryView::Context listContext() override;
void listScrollTo(int top) override; void listScrollTo(int top) override;
void listCloseRequest() override; void listCloseRequest() override;
rpl::producer<Data::MessagesSlice> listSource( rpl::producer<Data::MessagesSlice> listSource(

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "history/history.h" #include "history/history.h"
#include "history/view/history_view_message.h"
#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"
@ -141,7 +142,11 @@ void History::setCloudDraft(std::unique_ptr<Data::Draft> &&draft) {
Data::Draft *History::createCloudDraft(Data::Draft *fromDraft) { Data::Draft *History::createCloudDraft(Data::Draft *fromDraft) {
if (Data::draftIsNull(fromDraft)) { if (Data::draftIsNull(fromDraft)) {
setCloudDraft(std::make_unique<Data::Draft>(TextWithTags(), 0, MessageCursor(), false)); setCloudDraft(std::make_unique<Data::Draft>(
TextWithTags(),
0,
MessageCursor(),
false));
cloudDraft()->date = QDateTime(); cloudDraft()->date = QDateTime();
} else { } else {
auto existing = cloudDraft(); auto existing = cloudDraft();
@ -369,11 +374,11 @@ void ChannelHistory::getRangeDifference() {
auto fromId = MsgId(0); auto fromId = MsgId(0);
auto toId = MsgId(0); auto toId = MsgId(0);
for (auto blockIndex = 0, blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { for (auto blockIndex = 0, blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) {
auto block = blocks[blockIndex]; const auto &block = blocks[blockIndex];
for (auto itemIndex = 0, itemsCount = int(block->items.size()); itemIndex < itemsCount; ++itemIndex) { for (auto itemIndex = 0, itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) {
auto item = block->items[itemIndex]; const auto &message = block->messages[itemIndex];
if (item->id > 0) { if (message->id() > 0) {
fromId = item->id; fromId = message->id();
break; break;
} }
} }
@ -382,11 +387,11 @@ void ChannelHistory::getRangeDifference() {
if (!fromId) return; if (!fromId) return;
for (auto blockIndex = blocks.size(); blockIndex > 0;) { for (auto blockIndex = blocks.size(); blockIndex > 0;) {
auto block = blocks[--blockIndex]; const auto &block = blocks[--blockIndex];
for (auto itemIndex = block->items.size(); itemIndex > 0;) { for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
auto item = block->items[--itemIndex]; const auto &message = block->messages[--itemIndex];
if (item->id > 0) { if (message->id() > 0) {
toId = item->id; toId = message->id();
break; break;
} }
} }
@ -438,9 +443,9 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) {
} }
for (auto blockIndex = blocks.size(); blockIndex > 0;) { for (auto blockIndex = blocks.size(); blockIndex > 0;) {
auto block = blocks[--blockIndex]; const auto &block = blocks[--blockIndex];
for (auto itemIndex = block->items.size(); itemIndex > 0;) { for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
auto item = block->items[--itemIndex]; const auto item = block->messages[--itemIndex]->data();
// Due to a server bug sometimes inviteDate is less (before) than the // Due to a server bug sometimes inviteDate is less (before) than the
// first message in the megagroup (message about migration), let us // first message in the megagroup (message about migration), let us
@ -497,8 +502,8 @@ void ChannelHistory::checkJoinedMessage(bool createUnread) {
QDateTime inviteDate = peer->asChannel()->inviteDate; QDateTime inviteDate = peer->asChannel()->inviteDate;
QDateTime firstDate, lastDate; QDateTime firstDate, lastDate;
if (!blocks.empty()) { if (!blocks.empty()) {
firstDate = blocks.front()->items.front()->date; firstDate = blocks.front()->messages.front()->data()->date;
lastDate = blocks.back()->items.back()->date; lastDate = blocks.back()->messages.back()->data()->date;
} }
if (!firstDate.isNull() && !lastDate.isNull() && (firstDate <= inviteDate || loadedAtTop()) && (lastDate > inviteDate || loadedAtBottom())) { if (!firstDate.isNull() && !lastDate.isNull() && (firstDate <= inviteDate || loadedAtTop()) && (lastDate > inviteDate || loadedAtBottom())) {
bool willBeLastMsg = (inviteDate >= lastDate); bool willBeLastMsg = (inviteDate >= lastDate);
@ -514,9 +519,9 @@ void ChannelHistory::checkMaxReadMessageDate() {
if (_maxReadMessageDate.isValid()) return; if (_maxReadMessageDate.isValid()) return;
for (auto blockIndex = blocks.size(); blockIndex > 0;) { for (auto blockIndex = blocks.size(); blockIndex > 0;) {
auto block = blocks[--blockIndex]; const auto &block = blocks[--blockIndex];
for (auto itemIndex = block->items.size(); itemIndex > 0;) { for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
auto item = block->items[--itemIndex]; const auto item = block->messages[--itemIndex]->data();
if (!item->unread()) { if (!item->unread()) {
_maxReadMessageDate = item->date; _maxReadMessageDate = item->date;
if (item->isGroupMigrate() && isMegagroup() && peer->migrateFrom()) { if (item->isGroupMigrate() && isMegagroup() && peer->migrateFrom()) {
@ -1339,29 +1344,26 @@ HistoryBlock *History::prepareBlockForAddingItem() {
return _buildingFrontBlock->block; return _buildingFrontBlock->block;
} }
auto result = _buildingFrontBlock->block = new HistoryBlock(this); blocks.push_front(std::make_unique<HistoryBlock>(this));
if (_buildingFrontBlock->expectedItemsCount > 0) { for (auto i = 0, l = int(blocks.size()); i != l; ++i) {
result->items.reserve(_buildingFrontBlock->expectedItemsCount + 1);
}
result->setIndexInHistory(0);
blocks.push_front(result);
for (int i = 1, l = blocks.size(); i < l; ++i) {
blocks[i]->setIndexInHistory(i); blocks[i]->setIndexInHistory(i);
} }
return result; _buildingFrontBlock->block = blocks.front().get();
if (_buildingFrontBlock->expectedItemsCount > 0) {
_buildingFrontBlock->block->messages.reserve(
_buildingFrontBlock->expectedItemsCount + 1);
}
return _buildingFrontBlock->block;
} }
auto addNewBlock = blocks.empty() || (blocks.back()->items.size() >= kNewBlockEachMessage); const auto addNewBlock = blocks.empty()
if (!addNewBlock) { || (blocks.back()->messages.size() >= kNewBlockEachMessage);
return blocks.back(); if (addNewBlock) {
blocks.push_back(std::make_unique<HistoryBlock>(this));
blocks.back()->setIndexInHistory(blocks.size() - 1);
blocks.back()->messages.reserve(kNewBlockEachMessage);
} }
return blocks.back().get();
auto result = new HistoryBlock(this);
result->setIndexInHistory(blocks.size());
blocks.push_back(result);
result->items.reserve(kNewBlockEachMessage);
return result;
}; };
void History::addItemToBlock(not_null<HistoryItem*> item) { void History::addItemToBlock(not_null<HistoryItem*> item) {
@ -1369,8 +1371,10 @@ void History::addItemToBlock(not_null<HistoryItem*> item) {
auto block = prepareBlockForAddingItem(); auto block = prepareBlockForAddingItem();
item->attachToBlock(block, block->items.size()); block->messages.push_back(std::make_unique<HistoryView::Message>(
block->items.push_back(item); item,
HistoryView::Context::History));
item->attachToBlock(block, block->messages.size() - 1);
item->previousItemChanged(); item->previousItemChanged();
if (isBuildingFrontBlock() && _buildingFrontBlock->expectedItemsCount > 0) { if (isBuildingFrontBlock() && _buildingFrontBlock->expectedItemsCount > 0) {
@ -1379,62 +1383,11 @@ void History::addItemToBlock(not_null<HistoryItem*> item) {
} }
template <int kSharedMediaTypeCount> template <int kSharedMediaTypeCount>
void History::addToSharedMedia(std::vector<MsgId> (&medias)[kSharedMediaTypeCount], bool force) { void History::addToSharedMedia(
std::vector<MsgId> (&medias)[kSharedMediaTypeCount],
bool force) {
auto from = loadedAtTop() ? 0 : minMsgId(); auto from = loadedAtTop() ? 0 : minMsgId();
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
if (from > till) {
// History is desync, nothing good can be added.
//// Logging
auto value = QStringList();
for (auto block : blocks) {
auto indices = QStringList();
auto &items = block->items;
auto count = int(items.size());
auto logItem = [&](auto &&item) {
indices.push_back(QString::number(item->id));
};
if (count < 4) {
for (auto item : items) {
logItem(item);
}
} else {
auto last = 0;
auto logLast = [&] {
logItem(items[last]);
};
auto logTill = [&](int till) {
if (last < till - 1) {
indices.push_back("...["
+ QString::number(till - 1 - last)
+ "]...");
}
last = till;
logLast();
};
auto badPair = [&](int index) {
auto prev = items[index - 1]->id;
auto next = items[index]->id;
return IsServerMsgId(prev)
&& IsServerMsgId(next)
&& (next < prev);
};
logLast();
for (auto i = 1; i != count - 1; ++i) {
if (badPair(i) || badPair(i + 1)) {
logTill(i);
}
}
logTill(count - 1);
}
value.push_back(indices.join(","));
}
CrashReports::SetAnnotation("full", value.join(";"));
Assert(!"History desync caught!");
//// Logging
return;
}
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) { for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
if (force || !medias[i].empty()) { if (force || !medias[i].empty()) {
auto type = static_cast<Storage::SharedMediaType>(i); auto type = static_cast<Storage::SharedMediaType>(i);
@ -1507,8 +1460,8 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
// lastParticipants are displayed in Profile as members list. // lastParticipants are displayed in Profile as members list.
markupSenders = &peer->asChannel()->mgInfo->markupSenders; markupSenders = &peer->asChannel()->mgInfo->markupSenders;
} }
for (auto i = block->items.size(); i > 0; --i) { for (auto i = block->messages.size(); i > 0; --i) {
auto item = block->items[i - 1]; const auto item = block->messages[i - 1]->data();
item->addToUnreadMentions(UnreadMentionType::Existing); item->addToUnreadMentions(UnreadMentionType::Existing);
if (item->from()->id) { if (item->from()->id) {
if (lastAuthors) { // chats if (lastAuthors) { // chats
@ -1678,8 +1631,9 @@ void History::checkAddAllToUnreadMentions() {
return; return;
} }
for (const auto block : blocks) { for (const auto &block : blocks) {
for (const auto item : block->items) { for (const auto &message : block->messages) {
const auto item = message->data();
item->addToUnreadMentions(UnreadMentionType::Existing); item->addToUnreadMentions(UnreadMentionType::Existing);
} }
} }
@ -1688,13 +1642,14 @@ void History::checkAddAllToUnreadMentions() {
void History::addBlockToSharedMedia(HistoryBlock *block) { void History::addBlockToSharedMedia(HistoryBlock *block) {
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount]; std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
if (block) { if (block) {
for (auto item : block->items) { for (const auto &message : block->messages) {
const auto item = message->data();
if (auto types = item->sharedMediaTypes()) { if (auto types = item->sharedMediaTypes()) {
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) { for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
auto type = static_cast<Storage::SharedMediaType>(i); auto type = static_cast<Storage::SharedMediaType>(i);
if (types.test(type)) { if (types.test(type)) {
if (medias[i].empty()) { if (medias[i].empty()) {
medias[i].reserve(block->items.size()); medias[i].reserve(block->messages.size());
} }
medias[i].push_back(item->id); medias[i].push_back(item->id);
} }
@ -1709,11 +1664,13 @@ int History::countUnread(MsgId upTo) {
int result = 0; int result = 0;
for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) { for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) {
--i; --i;
for (auto j = (*i)->items.cend(), en = (*i)->items.cbegin(); j != en;) { const auto &messages = (*i)->messages;
for (auto j = messages.cend(), en = messages.cbegin(); j != en;) {
--j; --j;
if ((*j)->id > 0 && (*j)->id <= upTo) { const auto item = (*j)->data();
if (item->id > 0 && item->id <= upTo) {
break; break;
} else if (!(*j)->out() && (*j)->unread() && (*j)->id > upTo) { } else if (!item->out() && item->unread() && item->id > upTo) {
++result; ++result;
} }
} }
@ -1726,11 +1683,13 @@ void History::updateShowFrom() {
for (auto i = blocks.cend(); i != blocks.cbegin();) { for (auto i = blocks.cend(); i != blocks.cbegin();) {
--i; --i;
for (auto j = (*i)->items.cend(); j != (*i)->items.cbegin();) { const auto &messages = (*i)->messages;
for (auto j = messages.cend(); j != messages.cbegin();) {
--j; --j;
if ((*j)->id > 0 && (!(*j)->out() || !showFrom)) { const auto item = (*j)->data();
if ((*j)->id >= inboxReadBefore) { if (item->id > 0 && (!item->out() || !showFrom)) {
showFrom = *j; if (item->id >= inboxReadBefore) {
showFrom = item;
} else { } else {
return; return;
} }
@ -1779,7 +1738,7 @@ MsgId History::outboxRead(HistoryItem *wasRead) {
} }
HistoryItem *History::lastAvailableMessage() const { HistoryItem *History::lastAvailableMessage() const {
return isEmpty() ? nullptr : blocks.back()->items.back(); return isEmpty() ? nullptr : blocks.back()->messages.back()->data().get();
} }
void History::setUnreadCount(int newUnreadCount) { void History::setUnreadCount(int newUnreadCount) {
@ -1839,21 +1798,28 @@ bool History::changeMute(bool newMute) {
} }
void History::getNextShowFrom(HistoryBlock *block, int i) { void History::getNextShowFrom(HistoryBlock *block, int i) {
const auto setFromMessage = [&](const auto &message) {
const auto item = message->data();
if (item->id > 0) {
showFrom = item;
return true;
}
return false;
};
if (i >= 0) { if (i >= 0) {
auto l = block->items.size(); auto l = block->messages.size();
for (++i; i < l; ++i) { for (++i; i < l; ++i) {
if (block->items[i]->id > 0) { const auto &message = block->messages[i];
showFrom = block->items[i]; if (setFromMessage(block->messages[i])) {
return; return;
} }
} }
} }
for (auto j = block->indexInHistory() + 1, s = int(blocks.size()); j < s; ++j) { for (auto j = block->indexInHistory() + 1, s = int(blocks.size()); j < s; ++j) {
block = blocks[j]; block = blocks[j].get();
for_const (auto item, block->items) { for (const auto &message : block->messages) {
if (item->id > 0) { if (setFromMessage(message)) {
showFrom = item;
return; return;
} }
} }
@ -1874,7 +1840,7 @@ QDateTime History::adjustChatListDate() const {
void History::countScrollState(int top) { void History::countScrollState(int top) {
countScrollTopItem(top); countScrollTopItem(top);
if (scrollTopItem) { if (scrollTopItem) {
scrollTopOffset = (top - scrollTopItem->block()->y() - scrollTopItem->y()); scrollTopOffset = (top - scrollTopItem->data()->block()->y() - scrollTopItem->y());
} }
} }
@ -1884,64 +1850,65 @@ void History::countScrollTopItem(int top) {
return; return;
} }
int itemIndex = 0, blockIndex = 0, itemTop = 0; auto itemIndex = 0;
if (scrollTopItem && !scrollTopItem->detached()) { auto blockIndex = 0;
itemIndex = scrollTopItem->indexInBlock(); auto itemTop = 0;
blockIndex = scrollTopItem->block()->indexInHistory(); if (scrollTopItem) {
itemIndex = scrollTopItem->data()->indexInBlock();
blockIndex = scrollTopItem->data()->block()->indexInHistory();
itemTop = blocks[blockIndex]->y() + scrollTopItem->y(); itemTop = blocks[blockIndex]->y() + scrollTopItem->y();
} }
if (itemTop > top) { if (itemTop > top) {
// go backward through history while we don't find an item that starts above // go backward through history while we don't find an item that starts above
do { do {
auto block = blocks[blockIndex]; const auto &block = blocks[blockIndex];
for (--itemIndex; itemIndex >= 0; --itemIndex) { for (--itemIndex; itemIndex >= 0; --itemIndex) {
auto item = block->items[itemIndex]; const auto view = block->messages[itemIndex].get();
itemTop = block->y() + item->y(); itemTop = block->y() + view->y();
if (itemTop <= top) { if (itemTop <= top) {
scrollTopItem = item; scrollTopItem = view;
return; return;
} }
} }
if (--blockIndex >= 0) { if (--blockIndex >= 0) {
itemIndex = blocks[blockIndex]->items.size(); itemIndex = blocks[blockIndex]->messages.size();
} else { } else {
break; break;
} }
} while (true); } while (true);
scrollTopItem = blocks.front()->items.front(); scrollTopItem = blocks.front()->messages.front().get();
} else { } else {
// go forward through history while we don't find the last item that starts above // go forward through history while we don't find the last item that starts above
for (int blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) { for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) {
auto block = blocks[blockIndex]; const auto &block = blocks[blockIndex];
for (int itemsCount = block->items.size(); itemIndex < itemsCount; ++itemIndex) { for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) {
auto item = block->items[itemIndex]; itemTop = block->y() + block->messages[itemIndex]->y();
itemTop = block->y() + item->y();
if (itemTop > top) { if (itemTop > top) {
Assert(itemIndex > 0 || blockIndex > 0); Assert(itemIndex > 0 || blockIndex > 0);
if (itemIndex > 0) { if (itemIndex > 0) {
scrollTopItem = block->items[itemIndex - 1]; scrollTopItem = block->messages[itemIndex - 1].get();
} else { } else {
scrollTopItem = blocks[blockIndex - 1]->items.back(); scrollTopItem = blocks[blockIndex - 1]->messages.back().get();
} }
return; return;
} }
} }
itemIndex = 0; itemIndex = 0;
} }
scrollTopItem = blocks.back()->items.back(); scrollTopItem = blocks.back()->messages.back().get();
} }
} }
void History::getNextScrollTopItem(HistoryBlock *block, int32 i) { void History::getNextScrollTopItem(HistoryBlock *block, int32 i) {
++i; ++i;
if (i > 0 && i < block->items.size()) { if (i > 0 && i < block->messages.size()) {
scrollTopItem = block->items[i]; scrollTopItem = block->messages[i].get();
return; return;
} }
int j = block->indexInHistory() + 1; int j = block->indexInHistory() + 1;
if (j > 0 && j < blocks.size()) { if (j > 0 && j < blocks.size()) {
scrollTopItem = blocks[j]->items.front(); scrollTopItem = blocks[j]->messages.front().get();
return; return;
} }
scrollTopItem = nullptr; scrollTopItem = nullptr;
@ -1966,24 +1933,29 @@ void History::destroyUnreadBar() {
} }
} }
not_null<HistoryItem*> History::addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex) { not_null<HistoryItem*> History::addNewInTheMiddle(
not_null<HistoryItem*> newItem,
int blockIndex,
int itemIndex) {
Expects(blockIndex >= 0); Expects(blockIndex >= 0);
Expects(blockIndex < blocks.size()); Expects(blockIndex < blocks.size());
Expects(itemIndex >= 0); Expects(itemIndex >= 0);
Expects(itemIndex <= blocks[blockIndex]->items.size()); Expects(itemIndex <= blocks[blockIndex]->messages.size());
auto block = blocks[blockIndex]; const auto &block = blocks[blockIndex];
newItem->attachToBlock(block, itemIndex); block->messages.insert(
block->items.insert(block->items.begin() + itemIndex, newItem); block->messages.begin() + itemIndex,
std::make_unique<HistoryView::Message>(newItem, HistoryView::Context::History));
newItem->attachToBlock(block.get(), itemIndex);
newItem->previousItemChanged(); newItem->previousItemChanged();
if (itemIndex + 1 < block->items.size()) { if (itemIndex + 1 < block->messages.size()) {
for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) { for (auto i = itemIndex + 1, l = int(block->messages.size()); i != l; ++i) {
block->items[i]->setIndexInBlock(i); block->messages[i]->data()->setIndexInBlock(i);
} }
block->items[itemIndex + 1]->previousItemChanged(); block->messages[itemIndex + 1]->data()->previousItemChanged();
} else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->items.empty()) { } else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->messages.empty()) {
blocks[blockIndex + 1]->items.front()->previousItemChanged(); blocks[blockIndex + 1]->messages.front()->data()->previousItemChanged();
} else { } else {
newItem->nextItemChanged(); newItem->nextItemChanged();
} }
@ -2001,10 +1973,10 @@ HistoryItem *History::findNextItem(not_null<HistoryItem*> item) const {
const auto nextBlockIndex = item->block()->indexInHistory() + 1; const auto nextBlockIndex = item->block()->indexInHistory() + 1;
const auto nextItemIndex = item->indexInBlock() + 1; const auto nextItemIndex = item->indexInBlock() + 1;
if (nextItemIndex < int(item->block()->items.size())) { if (nextItemIndex < int(item->block()->messages.size())) {
return item->block()->items[nextItemIndex]; return item->block()->messages[nextItemIndex]->data();
} else if (nextBlockIndex < int(blocks.size())) { } else if (nextBlockIndex < int(blocks.size())) {
return blocks[nextBlockIndex]->items.front(); return blocks[nextBlockIndex]->messages.front()->data();
} }
return nullptr; return nullptr;
} }
@ -2015,9 +1987,9 @@ HistoryItem *History::findPreviousItem(not_null<HistoryItem*> item) const {
const auto blockIndex = item->block()->indexInHistory(); const auto blockIndex = item->block()->indexInHistory();
const auto itemIndex = item->indexInBlock(); const auto itemIndex = item->indexInBlock();
if (itemIndex > 0) { if (itemIndex > 0) {
return item->block()->items[itemIndex - 1]; return item->block()->messages[itemIndex - 1]->data();
} else if (blockIndex > 0) { } else if (blockIndex > 0) {
return blocks[blockIndex - 1]->items.back(); return blocks[blockIndex - 1]->messages.back()->data();
} }
return nullptr; return nullptr;
} }
@ -2162,14 +2134,14 @@ HistoryBlock *History::finishBuildingFrontBlock() {
auto block = _buildingFrontBlock->block; auto block = _buildingFrontBlock->block;
if (block) { if (block) {
if (blocks.size() > 1) { if (blocks.size() > 1) {
auto last = block->items.back(); // ... item, item, item, last ], [ first, item, item ... const auto last = block->messages.back()->data(); // ... item, item, item, last ], [ first, item, item ...
auto first = blocks[1]->items.front(); const auto first = blocks[1]->messages.front()->data();
// we've added a new front block, so previous item for // we've added a new front block, so previous item for
// the old first item of a first block was changed // the old first item of a first block was changed
first->previousItemChanged(); first->previousItemChanged();
} else { } else {
block->items.back()->nextItemChanged(); block->messages.back()->data()->nextItemChanged();
} }
} }
@ -2288,8 +2260,9 @@ void History::fixLastMessage(bool wasAtBottom) {
} }
MsgId History::minMsgId() const { MsgId History::minMsgId() const {
for (auto block : std::as_const(blocks)) { for (const auto &block : blocks) {
for (auto item : std::as_const(block->items)) { for (const auto &message : block->messages) {
const auto item = message->data();
if (IsServerMsgId(item->id)) { if (IsServerMsgId(item->id)) {
return item->id; return item->id;
} }
@ -2299,8 +2272,9 @@ MsgId History::minMsgId() const {
} }
MsgId History::maxMsgId() const { MsgId History::maxMsgId() const {
for (auto block : base::reversed(std::as_const(blocks))) { for (const auto &block : base::reversed(blocks)) {
for (auto item : base::reversed(std::as_const(block->items))) { for (const auto &message : base::reversed(block->messages)) {
const auto item = message->data();
if (IsServerMsgId(item->id)) { if (IsServerMsgId(item->id)) {
return item->id; return item->id;
} }
@ -2325,7 +2299,7 @@ int History::resizeGetHeight(int newWidth) {
width = newWidth; width = newWidth;
int y = 0; int y = 0;
for_const (auto block, blocks) { for (const auto &block : blocks) {
block->setY(y); block->setY(y);
y += block->resizeGetHeight(newWidth, resizeAllItems); y += block->resizeGetHeight(newWidth, resizeAllItems);
} }
@ -2357,7 +2331,9 @@ History *History::migrateFrom() const {
} }
bool History::isDisplayedEmpty() const { bool History::isDisplayedEmpty() const {
return isEmpty() || ((blocks.size() == 1) && blocks.front()->items.size() == 1 && blocks.front()->items.front()->isEmpty()); return isEmpty() || ((blocks.size() == 1)
&& blocks.front()->messages.size() == 1
&& blocks.front()->messages.front()->data()->isEmpty());
} }
bool History::hasOrphanMediaGroupPart() const { bool History::hasOrphanMediaGroupPart() const {
@ -2365,10 +2341,10 @@ bool History::hasOrphanMediaGroupPart() const {
return false; return false;
} else if (blocks.size() != 1) { } else if (blocks.size() != 1) {
return false; return false;
} else if (blocks.front()->items.size() != 1) { } else if (blocks.front()->messages.size() != 1) {
return false; return false;
} }
return blocks.front()->items.front()->groupId() != MessageGroupId(); return blocks.front()->messages.front()->data()->groupId() != MessageGroupId();
} }
bool History::removeOrphanMediaGroupPart() { bool History::removeOrphanMediaGroupPart() {
@ -2444,8 +2420,8 @@ void History::clearUpTill(MsgId availableMinId) {
return; return;
} }
do { do {
auto item = blocks.front()->items.front(); const auto item = blocks.front()->messages.front()->data();
auto itemId = item->id; const auto itemId = item->id;
if (IsServerMsgId(itemId) && itemId >= availableMinId) { if (IsServerMsgId(itemId) && itemId >= availableMinId) {
if (itemId == availableMinId) { if (itemId == availableMinId) {
auto fromId = 0; auto fromId = 0;
@ -2472,21 +2448,19 @@ void History::clearUpTill(MsgId availableMinId) {
void History::applyGroupAdminChanges( void History::applyGroupAdminChanges(
const base::flat_map<UserId, bool> &changes) { const base::flat_map<UserId, bool> &changes) {
for (auto block : blocks) { for (const auto &block : blocks) {
for (auto item : block->items) { for (const auto &message : block->messages) {
item->applyGroupAdminChanges(changes); message->data()->applyGroupAdminChanges(changes);
} }
} }
} }
void History::clearBlocks(bool leaveItems) { void History::clearBlocks(bool leaveItems) {
Blocks lst; const auto cleared = base::take(blocks);
std::swap(lst, blocks); for (const auto &block : cleared) {
for_const (HistoryBlock *block, lst) {
if (leaveItems) { if (leaveItems) {
block->clear(true); block->clear(true);
} }
delete block;
} }
} }
@ -2518,7 +2492,7 @@ void History::changeMsgId(MsgId oldId, MsgId newId) {
} }
void History::removeBlock(not_null<HistoryBlock*> block) { void History::removeBlock(not_null<HistoryBlock*> block) {
Expects(block->items.empty()); Expects(block->messages.empty());
if (_buildingFrontBlock && block == _buildingFrontBlock->block) { if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
_buildingFrontBlock->block = nullptr; _buildingFrontBlock->block = nullptr;
@ -2530,9 +2504,9 @@ void History::removeBlock(not_null<HistoryBlock*> block) {
for (int i = index, l = blocks.size(); i < l; ++i) { for (int i = index, l = blocks.size(); i < l; ++i) {
blocks[i]->setIndexInHistory(i); blocks[i]->setIndexInHistory(i);
} }
blocks[index]->items.front()->previousItemChanged(); blocks[index]->messages.front()->data()->previousItemChanged();
} else if (!blocks.empty() && !blocks.back()->items.empty()) { } else if (!blocks.empty() && !blocks.back()->messages.empty()) {
blocks.back()->items.back()->nextItemChanged(); blocks.back()->messages.back()->data()->nextItemChanged();
} }
} }
@ -2540,10 +2514,16 @@ History::~History() {
clearOnDestroy(); clearOnDestroy();
} }
HistoryBlock::HistoryBlock(not_null<History*> history)
: _history(history) {
}
int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) { int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) {
auto y = 0; auto y = 0;
for_const (auto item, items) { for (const auto &message : messages) {
item->setY(y); message->setY(y);
const auto item = message->data();
if (resizeAllItems || item->pendingResize()) { if (resizeAllItems || item->pendingResize()) {
y += item->resizeGetHeight(newWidth); y += item->resizeGetHeight(newWidth);
} else { } else {
@ -2555,17 +2535,14 @@ int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) {
} }
void HistoryBlock::clear(bool leaveItems) { void HistoryBlock::clear(bool leaveItems) {
auto itemsList = base::take(items); const auto list = base::take(messages);
if (leaveItems) { if (leaveItems) {
for_const (auto item, itemsList) { for (const auto &message : list) {
item->detachFast(); message->data()->detachFast();
}
} else {
for_const (auto item, itemsList) {
delete item;
} }
} }
// #TODO feeds delete all items in history
} }
void HistoryBlock::removeItem(not_null<HistoryItem*> item) { void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
@ -2594,30 +2571,31 @@ void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
if (_history->unreadBar == item) { if (_history->unreadBar == item) {
_history->unreadBar = nullptr; _history->unreadBar = nullptr;
} }
if (_history->scrollTopItem == item) { if (_history->scrollTopItem && _history->scrollTopItem->data() == item) {
_history->getNextScrollTopItem(this, itemIndex); _history->getNextScrollTopItem(this, itemIndex);
} }
item->detachFast(); item->detachFast();
items.erase(items.begin() + itemIndex); messages.erase(messages.begin() + itemIndex);
for (auto i = itemIndex, l = int(items.size()); i < l; ++i) { for (auto i = itemIndex, l = int(messages.size()); i < l; ++i) {
items[i]->setIndexInBlock(i); messages[i]->data()->setIndexInBlock(i);
} }
if (items.empty()) { if (messages.empty()) {
// Deletes this.
_history->removeBlock(this); _history->removeBlock(this);
} else if (itemIndex < items.size()) { } else if (itemIndex < messages.size()) {
items[itemIndex]->previousItemChanged(); messages[itemIndex]->data()->previousItemChanged();
} else if (blockIndex + 1 < _history->blocks.size()) { } else if (blockIndex + 1 < _history->blocks.size()) {
_history->blocks[blockIndex + 1]->items.front()->previousItemChanged(); _history->blocks[blockIndex + 1]->messages.front()->data()->previousItemChanged();
} else if (!_history->blocks.empty() && !_history->blocks.back()->items.empty()) { } else if (!_history->blocks.empty() && !_history->blocks.back()->messages.empty()) {
_history->blocks.back()->items.back()->nextItemChanged(); _history->blocks.back()->messages.back()->data()->nextItemChanged();
}
if (items.empty()) {
delete this;
} }
if (needGroupRecount) { if (needGroupRecount) {
groupHistory->recountGrouping(groupFrom, groupTill); groupHistory->recountGrouping(groupFrom, groupTill);
} }
} }
HistoryBlock::~HistoryBlock() {
clear();
}

View File

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/flat_set.h" #include "base/flat_set.h"
#include "base/flags.h" #include "base/flags.h"
class History;
class HistoryItem; class HistoryItem;
using HistoryItemsList = std::vector<not_null<HistoryItem*>>; using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
@ -26,7 +27,10 @@ enum NewMessageType {
NewMessageExisting, NewMessageExisting,
}; };
class History; namespace HistoryView {
class Message;
} // namespace HistoryView
class Histories { class Histories {
public: public:
using Map = QHash<PeerId, History*>; using Map = QHash<PeerId, History*>;
@ -320,8 +324,7 @@ public:
void eraseFromUnreadMentions(MsgId msgId); void eraseFromUnreadMentions(MsgId msgId);
void addUnreadMentionsSlice(const MTPmessages_Messages &result); void addUnreadMentionsSlice(const MTPmessages_Messages &result);
using Blocks = std::deque<HistoryBlock*>; std::deque<std::unique_ptr<HistoryBlock>> blocks;
Blocks blocks;
int width = 0; int width = 0;
int height = 0; int height = 0;
@ -394,7 +397,7 @@ public:
// we save a pointer of the history item at the top of the displayed window // we save a pointer of the history item at the top of the displayed window
// together with an offset from the window top to the top of this message // together with an offset from the window top to the top of this message
// resulting scrollTop = top(scrollTopItem) + scrollTopOffset // resulting scrollTop = top(scrollTopItem) + scrollTopOffset
HistoryItem *scrollTopItem = nullptr; HistoryView::Message *scrollTopItem = nullptr;
int scrollTopOffset = 0; int scrollTopOffset = 0;
void forgetScrollState() { void forgetScrollState() {
scrollTopItem = nullptr; scrollTopItem = nullptr;
@ -446,7 +449,10 @@ protected:
not_null<HistoryItem*> createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup); not_null<HistoryItem*> createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewItem(not_null<HistoryItem*> adding, bool newMsg); not_null<HistoryItem*> addNewItem(not_null<HistoryItem*> adding, bool newMsg);
not_null<HistoryItem*> addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex); not_null<HistoryItem*> addNewInTheMiddle(
not_null<HistoryItem*> newItem,
int blockIndex,
int itemIndex);
// All this methods add a new item to the first or last block // All this methods add a new item to the first or last block
// depending on if we are in isBuildingFronBlock() state. // depending on if we are in isBuildingFronBlock() state.
@ -573,18 +579,14 @@ private:
class HistoryBlock { class HistoryBlock {
public: public:
HistoryBlock(not_null<History*> history) : _history(history) { HistoryBlock(not_null<History*> history);
}
HistoryBlock(const HistoryBlock &) = delete; HistoryBlock(const HistoryBlock &) = delete;
HistoryBlock &operator=(const HistoryBlock &) = delete; HistoryBlock &operator=(const HistoryBlock &) = delete;
~HistoryBlock();
std::vector<HistoryItem*> items; std::vector<std::unique_ptr<HistoryView::Message>> messages;
void clear(bool leaveItems = false); void clear(bool leaveItems = false);
~HistoryBlock() {
clear();
}
void removeItem(not_null<HistoryItem*> item); void removeItem(not_null<HistoryItem*> item);
int resizeGetHeight(int newWidth, bool resizeAllItems); int resizeGetHeight(int newWidth, bool resizeAllItems);
@ -604,12 +606,16 @@ public:
HistoryBlock *previousBlock() const { HistoryBlock *previousBlock() const {
Expects(_indexInHistory >= 0); Expects(_indexInHistory >= 0);
return (_indexInHistory > 0) ? _history->blocks.at(_indexInHistory - 1) : nullptr; return (_indexInHistory > 0)
? _history->blocks[_indexInHistory - 1].get()
: nullptr;
} }
HistoryBlock *nextBlock() const { HistoryBlock *nextBlock() const {
Expects(_indexInHistory >= 0); Expects(_indexInHistory >= 0);
return (_indexInHistory + 1 < _history->blocks.size()) ? _history->blocks.at(_indexInHistory + 1) : nullptr; return (_indexInHistory + 1 < _history->blocks.size())
? _history->blocks[_indexInHistory + 1].get()
: nullptr;
} }
void setIndexInHistory(int index) { void setIndexInHistory(int index) {
_indexInHistory = index; _indexInHistory = index;
@ -617,7 +623,7 @@ public:
int indexInHistory() const { int indexInHistory() const {
Expects(_indexInHistory >= 0); Expects(_indexInHistory >= 0);
Expects(_indexInHistory < _history->blocks.size()); Expects(_indexInHistory < _history->blocks.size());
Expects(_history->blocks[_indexInHistory] == this); Expects(_history->blocks[_indexInHistory].get() == this);
return _indexInHistory; return _indexInHistory;
} }

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_media_types.h" #include "history/history_media_types.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_message.h"
#include "ui/text_options.h" #include "ui/text_options.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "window/window_controller.h" #include "window/window_controller.h"
@ -210,16 +211,16 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
auto blockIndex = BinarySearchBlocksOrItems<TopToBottom>(history->blocks, searchEdge - historytop); auto blockIndex = BinarySearchBlocksOrItems<TopToBottom>(history->blocks, searchEdge - historytop);
// Binary search for itemIndex of the first item that is not completely below the visible area. // Binary search for itemIndex of the first item that is not completely below the visible area.
auto block = history->blocks.at(blockIndex); auto block = history->blocks[blockIndex].get();
auto blocktop = historytop + block->y(); auto blocktop = historytop + block->y();
auto blockbottom = blocktop + block->height(); auto blockbottom = blocktop + block->height();
auto itemIndex = BinarySearchBlocksOrItems<TopToBottom>(block->items, searchEdge - blocktop); auto itemIndex = BinarySearchBlocksOrItems<TopToBottom>(block->messages, searchEdge - blocktop);
while (true) { while (true) {
while (true) { while (true) {
auto item = block->items.at(itemIndex); auto view = block->messages[itemIndex].get();
auto itemtop = blocktop + item->y(); auto itemtop = blocktop + view->y();
auto itembottom = itemtop + item->height(); auto itembottom = itemtop + view->data()->height();
// Binary search should've skipped all the items that are above / below the visible area. // Binary search should've skipped all the items that are above / below the visible area.
if (TopToBottom) { if (TopToBottom) {
@ -228,7 +229,7 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
Assert(itemtop < _visibleAreaBottom); Assert(itemtop < _visibleAreaBottom);
} }
if (!method(item, itemtop, itembottom)) { if (!method(view, itemtop, itembottom)) {
return; return;
} }
@ -244,7 +245,7 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
} }
if (TopToBottom) { if (TopToBottom) {
if (++itemIndex >= block->items.size()) { if (++itemIndex >= block->messages.size()) {
break; break;
} }
} else { } else {
@ -274,13 +275,13 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
return; return;
} }
} }
block = history->blocks[blockIndex]; block = history->blocks[blockIndex].get();
blocktop = historytop + block->y(); blocktop = historytop + block->y();
blockbottom = blocktop + block->height(); blockbottom = blocktop + block->height();
if (TopToBottom) { if (TopToBottom) {
itemIndex = 0; itemIndex = 0;
} else { } else {
itemIndex = block->items.size() - 1; itemIndex = block->messages.size() - 1;
} }
} }
} }
@ -295,9 +296,10 @@ void HistoryInner::enumerateUserpics(Method method) {
// -1 means we didn't find an attached to next message yet. // -1 means we didn't find an attached to next message yet.
int lowestAttachedItemTop = -1; int lowestAttachedItemTop = -1;
auto userpicCallback = [this, &lowestAttachedItemTop, &method](not_null<HistoryItem*> item, int itemtop, int itembottom) { auto userpicCallback = [&](not_null<Message*> view, int itemtop, int itembottom) {
// Skip all service messages. // Skip all service messages.
auto message = item->toHistoryMessage(); const auto item = view->data();
const auto message = item->toHistoryMessage();
if (!message) return true; if (!message) return true;
if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) { if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) {
@ -343,7 +345,8 @@ void HistoryInner::enumerateDates(Method method) {
// -1 means we didn't find a same-day with previous message yet. // -1 means we didn't find a same-day with previous message yet.
auto lowestInOneDayItemBottom = -1; auto lowestInOneDayItemBottom = -1;
auto dateCallback = [this, &lowestInOneDayItemBottom, &method, drawtop](not_null<HistoryItem*> item, int itemtop, int itembottom) { auto dateCallback = [&](not_null<Message*> view, int itemtop, int itembottom) {
const auto item = view->data();
if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) { if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) {
lowestInOneDayItemBottom = itembottom - item->marginBottom(); lowestInOneDayItemBottom = itembottom - item->marginBottom();
} }
@ -356,8 +359,8 @@ void HistoryInner::enumerateDates(Method method) {
if (itemtop > _visibleAreaTop) { if (itemtop > _visibleAreaTop) {
// Previous item (from the _migrated history) is drawing date now. // Previous item (from the _migrated history) is drawing date now.
return false; return false;
} else if (item == _history->blocks.front()->items.front() && item->isGroupMigrate() } else if (item == _history->blocks.front()->messages.front()->data() && item->isGroupMigrate()
&& _migrated->blocks.back()->items.back()->isGroupMigrate()) { && _migrated->blocks.back()->messages.back()->data()->isGroupMigrate()) {
// This item is completely invisible and should be completely ignored. // This item is completely invisible and should be completely ignored.
return false; return false;
} }
@ -430,12 +433,11 @@ TextSelection HistoryInner::computeRenderSelection(
} }
TextSelection HistoryInner::itemRenderSelection( TextSelection HistoryInner::itemRenderSelection(
not_null<HistoryItem*> item, not_null<Message*> view,
int selfromy, int selfromy,
int seltoy) const { int seltoy) const {
Expects(!item->detached()); const auto item = view->data();
const auto y = item->block()->y() + view->y();
const auto y = item->block()->y() + item->y();
if (y >= selfromy && y < seltoy) { if (y >= selfromy && y < seltoy) {
if (_dragSelecting && !item->serviceMsg() && item->id > 0) { if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
return FullSelection; return FullSelection;
@ -497,16 +499,17 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto hdrawtop = historyDrawTop(); auto hdrawtop = historyDrawTop();
if (mtop >= 0) { if (mtop >= 0) {
auto iBlock = (_curHistory == _migrated ? _curBlock : (_migrated->blocks.size() - 1)); auto iBlock = (_curHistory == _migrated ? _curBlock : (_migrated->blocks.size() - 1));
auto block = _migrated->blocks[iBlock]; auto block = _migrated->blocks[iBlock].get();
auto iItem = (_curHistory == _migrated ? _curItem : (block->items.size() - 1)); auto iItem = (_curHistory == _migrated ? _curItem : (block->messages.size() - 1));
auto item = block->items[iItem]; auto view = block->messages[iItem].get();
auto item = view->data();
auto y = mtop + block->y() + item->y(); auto y = mtop + block->y() + view->y();
p.save(); p.save();
p.translate(0, y); p.translate(0, y);
if (clip.y() < y + item->height()) while (y < drawToY) { if (clip.y() < y + item->height()) while (y < drawToY) {
const auto selection = itemRenderSelection( const auto selection = itemRenderSelection(
item, view,
selfromy - mtop, selfromy - mtop,
seltoy - mtop); seltoy - mtop);
item->draw(p, clip.translated(0, -y), selection, ms); item->draw(p, clip.translated(0, -y), selection, ms);
@ -524,33 +527,35 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
y += h; y += h;
++iItem; ++iItem;
if (iItem == block->items.size()) { if (iItem == block->messages.size()) {
iItem = 0; iItem = 0;
++iBlock; ++iBlock;
if (iBlock == _migrated->blocks.size()) { if (iBlock == _migrated->blocks.size()) {
break; break;
} }
block = _migrated->blocks[iBlock]; block = _migrated->blocks[iBlock].get();
} }
item = block->items[iItem]; view = block->messages[iItem].get();
item = view->data();
} }
p.restore(); p.restore();
} }
if (htop >= 0) { if (htop >= 0) {
auto iBlock = (_curHistory == _history ? _curBlock : 0); auto iBlock = (_curHistory == _history ? _curBlock : 0);
auto block = _history->blocks[iBlock]; auto block = _history->blocks[iBlock].get();
auto iItem = (_curHistory == _history ? _curItem : 0); auto iItem = (_curHistory == _history ? _curItem : 0);
auto item = block->items[iItem]; auto view = block->messages[iItem].get();
auto item = view->data();
auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height())); auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height()));
auto y = htop + block->y() + item->y(); auto y = htop + block->y() + view->y();
p.save(); p.save();
p.translate(0, y); p.translate(0, y);
while (y < drawToY) { while (y < drawToY) {
auto h = item->height(); auto h = item->height();
if (hclip.y() < y + h && hdrawtop < y + h) { if (hclip.y() < y + h && hdrawtop < y + h) {
const auto selection = itemRenderSelection( const auto selection = itemRenderSelection(
item, view,
selfromy - htop, selfromy - htop,
seltoy - htop); seltoy - htop);
item->draw(p, hclip.translated(0, -y), selection, ms); item->draw(p, hclip.translated(0, -y), selection, ms);
@ -567,15 +572,16 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
y += h; y += h;
++iItem; ++iItem;
if (iItem == block->items.size()) { if (iItem == block->messages.size()) {
iItem = 0; iItem = 0;
++iBlock; ++iBlock;
if (iBlock == _history->blocks.size()) { if (iBlock == _history->blocks.size()) {
break; break;
} }
block = _history->blocks[iBlock]; block = _history->blocks[iBlock].get();
} }
item = block->items[iItem]; view = block->messages[iItem].get();
item = view->data();
} }
p.restore(); p.restore();
} }
@ -601,7 +607,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
//QDate lastDate; //QDate lastDate;
//if (!_history->isEmpty()) { //if (!_history->isEmpty()) {
// lastDate = _history->blocks.back()->items.back()->date.date(); // lastDate = _history->blocks.back()->messages.back()->data()->date.date();
//} //}
//// if item top is before this value always show date as a floating date //// if item top is before this value always show date as a floating date
@ -1775,11 +1781,11 @@ void HistoryInner::recountHistoryGeometry() {
_historySkipHeight = 0; _historySkipHeight = 0;
if (_migrated) { if (_migrated) {
if (!_migrated->isEmpty() && !_history->isEmpty() && _migrated->loadedAtBottom() && _history->loadedAtTop()) { if (!_migrated->isEmpty() && !_history->isEmpty() && _migrated->loadedAtBottom() && _history->loadedAtTop()) {
if (_migrated->blocks.back()->items.back()->date.date() == _history->blocks.front()->items.front()->date.date()) { if (_migrated->blocks.back()->messages.back()->data()->date.date() == _history->blocks.front()->messages.front()->data()->date.date()) {
if (_migrated->blocks.back()->items.back()->isGroupMigrate() && _history->blocks.front()->items.front()->isGroupMigrate()) { if (_migrated->blocks.back()->messages.back()->data()->isGroupMigrate() && _history->blocks.front()->messages.front()->data()->isGroupMigrate()) {
_historySkipHeight += _history->blocks.front()->items.front()->height(); _historySkipHeight += _history->blocks.front()->messages.front()->data()->height();
} else { } else {
_historySkipHeight += _history->blocks.front()->items.front()->displayedDateHeight(); _historySkipHeight += _history->blocks.front()->messages.front()->data()->displayedDateHeight();
} }
} }
} }
@ -1923,7 +1929,7 @@ void HistoryInner::onScrollDateCheck() {
auto newScrollDateItem = _history->scrollTopItem ? _history->scrollTopItem : (_migrated ? _migrated->scrollTopItem : nullptr); auto newScrollDateItem = _history->scrollTopItem ? _history->scrollTopItem : (_migrated ? _migrated->scrollTopItem : nullptr);
auto newScrollDateItemTop = _history->scrollTopItem ? _history->scrollTopOffset : (_migrated ? _migrated->scrollTopOffset : 0); auto newScrollDateItemTop = _history->scrollTopItem ? _history->scrollTopOffset : (_migrated ? _migrated->scrollTopOffset : 0);
//if (newScrollDateItem && !displayScrollDate()) { //if (newScrollDateItem && !displayScrollDate()) {
// if (!_history->isEmpty() && newScrollDateItem->date.date() == _history->blocks.back()->items.back()->date.date()) { // if (!_history->isEmpty() && newScrollDateItem->date.date() == _history->blocks.back()->messages.back()->data()->date.date()) {
// newScrollDateItem = nullptr; // newScrollDateItem = nullptr;
// } // }
//} //}
@ -2065,32 +2071,34 @@ void HistoryInner::adjustCurrent(int32 y, History *history) const {
++_curBlock; ++_curBlock;
_curItem = 0; _curItem = 0;
} }
auto block = history->blocks[_curBlock]; auto block = history->blocks[_curBlock].get();
if (_curItem >= block->items.size()) { if (_curItem >= block->messages.size()) {
_curItem = block->items.size() - 1; _curItem = block->messages.size() - 1;
} }
auto by = block->y(); auto by = block->y();
while (block->items[_curItem]->y() + by > y && _curItem > 0) { while (block->messages[_curItem]->y() + by > y && _curItem > 0) {
--_curItem; --_curItem;
} }
while (block->items[_curItem]->y() + block->items[_curItem]->height() + by <= y && _curItem + 1 < block->items.size()) { while (block->messages[_curItem]->y() + block->messages[_curItem]->data()->height() + by <= y && _curItem + 1 < block->messages.size()) {
++_curItem; ++_curItem;
} }
} }
HistoryItem *HistoryInner::prevItem(HistoryItem *item) { HistoryItem *HistoryInner::prevItem(HistoryItem *item) {
if (!item || item->detached()) return nullptr; if (!item || item->detached()) {
return nullptr;
}
HistoryBlock *block = item->block(); const auto block = item->block();
int blockIndex = block->indexInHistory(), itemIndex = item->indexInBlock(); int blockIndex = block->indexInHistory(), itemIndex = item->indexInBlock();
if (itemIndex > 0) { if (itemIndex > 0) {
return block->items.at(itemIndex - 1); return block->messages[itemIndex - 1]->data();
} }
if (blockIndex > 0) { if (blockIndex > 0) {
return item->history()->blocks.at(blockIndex - 1)->items.back(); return item->history()->blocks[blockIndex - 1]->messages.back()->data();
} }
if (item->history() == _history && _migrated && _history->loadedAtTop() && !_migrated->isEmpty() && _migrated->loadedAtBottom()) { if (item->history() == _history && _migrated && _history->loadedAtTop() && !_migrated->isEmpty() && _migrated->loadedAtBottom()) {
return _migrated->blocks.back()->items.back(); return _migrated->blocks.back()->messages.back()->data();
} }
return nullptr; return nullptr;
} }
@ -2098,16 +2106,16 @@ HistoryItem *HistoryInner::prevItem(HistoryItem *item) {
HistoryItem *HistoryInner::nextItem(HistoryItem *item) { HistoryItem *HistoryInner::nextItem(HistoryItem *item) {
if (!item || item->detached()) return nullptr; if (!item || item->detached()) return nullptr;
HistoryBlock *block = item->block(); const auto block = item->block();
int blockIndex = block->indexInHistory(), itemIndex = item->indexInBlock(); int blockIndex = block->indexInHistory(), itemIndex = item->indexInBlock();
if (itemIndex + 1 < block->items.size()) { if (itemIndex + 1 < block->messages.size()) {
return block->items.at(itemIndex + 1); return block->messages[itemIndex + 1]->data();
} }
if (blockIndex + 1 < item->history()->blocks.size()) { if (blockIndex + 1 < item->history()->blocks.size()) {
return item->history()->blocks.at(blockIndex + 1)->items.front(); return item->history()->blocks[blockIndex + 1]->messages.front()->data();
} }
if (item->history() == _migrated && _history && _migrated->loadedAtBottom() && _history->loadedAtTop() && !_history->isEmpty()) { if (item->history() == _migrated && _history && _migrated->loadedAtBottom() && _history->loadedAtTop() && !_history->isEmpty()) {
return _history->blocks.front()->items.front(); return _history->blocks.front()->messages.front()->data();
} }
return nullptr; return nullptr;
} }
@ -2202,8 +2210,8 @@ void HistoryInner::onUpdateSelected() {
adjustCurrent(point.y()); adjustCurrent(point.y());
if (_curHistory && !_curHistory->isEmpty()) { if (_curHistory && !_curHistory->isEmpty()) {
block = _curHistory->blocks[_curBlock]; block = _curHistory->blocks[_curBlock].get();
item = block->items[_curItem]; item = block->messages[_curItem]->data();
App::mousedItem(item); App::mousedItem(item);
m = mapPointToItem(point, item); m = mapPointToItem(point, item);
@ -2476,12 +2484,10 @@ int HistoryInner::historyScrollTop() const {
auto htop = historyTop(); auto htop = historyTop();
auto mtop = migratedTop(); auto mtop = migratedTop();
if (htop >= 0 && _history->scrollTopItem) { if (htop >= 0 && _history->scrollTopItem) {
Assert(!_history->scrollTopItem->detached()); return htop + _history->scrollTopItem->data()->block()->y() + _history->scrollTopItem->y() + _history->scrollTopOffset;
return htop + _history->scrollTopItem->block()->y() + _history->scrollTopItem->y() + _history->scrollTopOffset;
} }
if (mtop >= 0 && _migrated->scrollTopItem) { if (mtop >= 0 && _migrated->scrollTopItem) {
Assert(!_migrated->scrollTopItem->detached()); return mtop + _migrated->scrollTopItem->data()->block()->y() + _migrated->scrollTopItem->y() + _migrated->scrollTopOffset;
return mtop + _migrated->scrollTopItem->block()->y() + _migrated->scrollTopItem->y() + _migrated->scrollTopOffset;
} }
return ScrollMax; return ScrollMax;
} }
@ -2505,7 +2511,7 @@ int HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not b
if (item->detached()) return -1; if (item->detached()) return -1;
auto top = (item->history() == _history) ? historyTop() : (item->history() == _migrated ? migratedTop() : -2); auto top = (item->history() == _history) ? historyTop() : (item->history() == _migrated ? migratedTop() : -2);
return (top < 0) ? top : (top + item->y() + item->block()->y()); return (top < 0) ? top : (top + item->mainView()->y() + item->block()->y());
} }
void HistoryInner::notifyIsBotChanged() { void HistoryInner::notifyIsBotChanged() {
@ -2531,7 +2537,10 @@ void HistoryInner::notifyMigrateUpdated() {
_migrated = _history->migrateFrom(); _migrated = _history->migrateFrom();
} }
int HistoryInner::moveScrollFollowingInlineKeyboard(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop) { int HistoryInner::moveScrollFollowingInlineKeyboard(
const HistoryItem *item,
int oldKeyboardTop,
int newKeyboardTop) {
if (item == App::mousedItem()) { if (item == App::mousedItem()) {
int top = itemTop(item); int top = itemTop(item);
if (top >= oldKeyboardTop) { if (top >= oldKeyboardTop) {
@ -2706,9 +2715,9 @@ void HistoryInner::addSelectionRange(
int toitem) const { int toitem) const {
if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) { if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) {
for (; fromblock <= toblock; ++fromblock) { for (; fromblock <= toblock; ++fromblock) {
auto block = history->blocks[fromblock]; auto block = history->blocks[fromblock].get();
for (int cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) { for (int cnt = (fromblock < toblock) ? block->messages.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto item = block->items[fromitem]; auto item = block->messages[fromitem]->data();
changeSelectionAsGroup(toItems, item, SelectAction::Select); changeSelectionAsGroup(toItems, item, SelectAction::Select);
} }
if (toItems->size() >= MaxSelectedItems) break; if (toItems->size() >= MaxSelectedItems) break;
@ -2743,7 +2752,7 @@ void HistoryInner::applyDragSelection(
toblock = -1; toblock = -1;
toitem = -1; toitem = -1;
} else { } else {
addSelectionRange(toItems, _migrated, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->items.size() - 1); addSelectionRange(toItems, _migrated, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->messages.size() - 1);
} }
fromblock = 0; fromblock = 0;
fromitem = 0; fromitem = 0;

View File

@ -122,7 +122,7 @@ private slots:
private: private:
class BotAbout; class BotAbout;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>; using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
using Message = HistoryView::Message;
enum class MouseAction { enum class MouseAction {
None, None,
PrepareDrag, PrepareDrag,
@ -161,7 +161,7 @@ private:
HistoryItem *nextItem(HistoryItem *item); HistoryItem *nextItem(HistoryItem *item);
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting); void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting);
TextSelection itemRenderSelection( TextSelection itemRenderSelection(
not_null<HistoryItem*> item, not_null<Message*> view,
int selfromy, int selfromy,
int seltoy) const; int seltoy) const;
TextSelection computeRenderSelection( TextSelection computeRenderSelection(
@ -291,7 +291,7 @@ private:
Animation _scrollDateOpacity; Animation _scrollDateOpacity;
SingleQueuedInvokation _scrollDateCheck; SingleQueuedInvokation _scrollDateCheck;
SingleTimer _scrollDateHideTimer; SingleTimer _scrollDateHideTimer;
HistoryItem *_scrollDateLastItem = nullptr; Message *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0; int _scrollDateLastItemTop = 0;
ClickHandlerPtr _scrollDateLink; ClickHandlerPtr _scrollDateLink;
@ -302,7 +302,7 @@ private:
// This function finds all history items that are displayed and calls template method // This function finds all history items that are displayed and calls template method
// for each found message (in given direction) in the passed history with passed top offset. // for each found message (in given direction) in the passed history with passed top offset.
// //
// Method has "bool (*Method)(not_null<HistoryItem*> item, int itemtop, int itembottom)" signature // Method has "bool (*Method)(not_null<Message*> view, int itemtop, int itembottom)" signature
// if it returns false the enumeration stops immidiately. // if it returns false the enumeration stops immidiately.
template <bool TopToBottom, typename Method> template <bool TopToBottom, typename Method>
void enumerateItemsInHistory(History *history, int historytop, Method method); void enumerateItemsInHistory(History *history, int historytop, Method method);

View File

@ -9,11 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "history/view/history_view_message.h"
#include "history/view/history_view_service_message.h"
#include "history/history_item_components.h" #include "history/history_item_components.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"
#include "history/history_message.h" #include "history/history_message.h"
#include "history/view/history_view_service_message.h"
#include "media/media_clip_reader.h" #include "media/media_clip_reader.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "styles/style_history.h" #include "styles/style_history.h"
@ -347,9 +348,22 @@ void HistoryItem::detach() {
_history->setPendingResize(); _history->setPendingResize();
} }
void HistoryItem::attachToBlock(not_null<HistoryBlock*> block, int index) {
Expects(!isLogEntry());
Expects(_block == nullptr);
Expects(_indexInBlock < 0);
Expects(index >= 0);
_block = block;
_indexInBlock = index;
_mainView = block->messages[index].get();
setPendingResize();
}
void HistoryItem::detachFast() { void HistoryItem::detachFast() {
_block = nullptr; _block = nullptr;
_indexInBlock = -1; _indexInBlock = -1;
_mainView = nullptr;
validateGroupId(); validateGroupId();
if (groupId()) { if (groupId()) {
@ -970,6 +984,32 @@ void HistoryItem::audioTrackUpdated() {
} }
} }
HistoryItem *HistoryItem::previousItem() const {
if (_block && _indexInBlock >= 0) {
if (_indexInBlock > 0) {
return _block->messages[_indexInBlock - 1]->data();
}
if (auto previous = _block->previousBlock()) {
Assert(!previous->messages.empty());
return previous->messages.back()->data();
}
}
return nullptr;
}
HistoryItem *HistoryItem::nextItem() const {
if (_block && _indexInBlock >= 0) {
if (_indexInBlock + 1 < _block->messages.size()) {
return _block->messages[_indexInBlock + 1]->data();
}
if (auto next = _block->nextBlock()) {
Assert(!next->messages.empty());
return next->messages.front()->data();
}
}
return nullptr;
}
void HistoryItem::recountDisplayDate() { void HistoryItem::recountDisplayDate() {
Expects(!isLogEntry()); Expects(!isLogEntry());
setDisplayDate([&] { setDisplayDate([&] {

View File

@ -232,23 +232,16 @@ public:
const HistoryBlock *block() const { const HistoryBlock *block() const {
return _block; return _block;
} }
HistoryView::Message *mainView() const {
return _mainView;
}
void destroy(); void destroy();
void detach(); void detach();
void detachFast(); void detachFast();
bool detached() const { bool detached() const {
return !_block; return !_block;
} }
void attachToBlock(HistoryBlock *block, int index) { void attachToBlock(not_null<HistoryBlock*> block, int index);
Expects(!isLogEntry());
Expects(_block == nullptr);
Expects(_indexInBlock < 0);
Expects(block != nullptr);
Expects(index >= 0);
_block = block;
_indexInBlock = index;
setPendingResize();
}
void setIndexInBlock(int index) { void setIndexInBlock(int index) {
Expects(_block != nullptr); Expects(_block != nullptr);
Expects(index >= 0); Expects(index >= 0);
@ -257,7 +250,7 @@ public:
} }
int indexInBlock() const { int indexInBlock() const {
Expects((_indexInBlock >= 0) == (_block != nullptr)); Expects((_indexInBlock >= 0) == (_block != nullptr));
Expects((_block == nullptr) || (_block->items[_indexInBlock] == this)); //Expects((_block == nullptr) || (_block->messages[_indexInBlock]->data() == this));
return _indexInBlock; return _indexInBlock;
} }
@ -423,12 +416,6 @@ public:
bool hasDirectLink() const; bool hasDirectLink() const;
QString directLink() const; QString directLink() const;
int y() const {
return _y;
}
void setY(int y) {
_y = y;
}
MsgId id; MsgId id;
QDateTime date; QDateTime date;
@ -569,30 +556,8 @@ public:
setAttachToNext(attachToNext); setAttachToNext(attachToNext);
} }
HistoryItem *previousItem() const { HistoryItem *previousItem() const;
if (_block && _indexInBlock >= 0) { HistoryItem *nextItem() const;
if (_indexInBlock > 0) {
return _block->items.at(_indexInBlock - 1);
}
if (auto previous = _block->previousBlock()) {
Assert(!previous->items.empty());
return previous->items.back();
}
}
return nullptr;
}
HistoryItem *nextItem() const {
if (_block && _indexInBlock >= 0) {
if (_indexInBlock + 1 < _block->items.size()) {
return _block->items.at(_indexInBlock + 1);
}
if (auto next = _block->nextBlock()) {
Assert(!next->items.empty());
return next->items.front();
}
}
return nullptr;
}
~HistoryItem(); ~HistoryItem();
@ -676,7 +641,7 @@ protected:
private: private:
void resetGroupMedia(const std::vector<not_null<HistoryItem*>> &others); void resetGroupMedia(const std::vector<not_null<HistoryItem*>> &others);
int _y = 0; HistoryView::Message *_mainView = nullptr;
int _width = 0; int _width = 0;
}; };

View File

@ -13,18 +13,25 @@ struct HistoryServiceDependentData {
ClickHandlerPtr lnk; ClickHandlerPtr lnk;
}; };
struct HistoryServicePinned : public RuntimeComponent<HistoryServicePinned>, public HistoryServiceDependentData { struct HistoryServicePinned
: public RuntimeComponent<HistoryServicePinned>
, public HistoryServiceDependentData {
}; };
struct HistoryServiceGameScore : public RuntimeComponent<HistoryServiceGameScore>, public HistoryServiceDependentData { struct HistoryServiceGameScore
: public RuntimeComponent<HistoryServiceGameScore>
, public HistoryServiceDependentData {
int score = 0; int score = 0;
}; };
struct HistoryServicePayment : public RuntimeComponent<HistoryServicePayment>, public HistoryServiceDependentData { struct HistoryServicePayment
: public RuntimeComponent<HistoryServicePayment>
, public HistoryServiceDependentData {
QString amount; QString amount;
}; };
struct HistoryServiceSelfDestruct : public RuntimeComponent<HistoryServiceSelfDestruct> { struct HistoryServiceSelfDestruct
: public RuntimeComponent<HistoryServiceSelfDestruct> {
enum class Type { enum class Type {
Photo, Photo,
Video, Video,

View File

@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_inner_widget.h" #include "history/history_inner_widget.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_message.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"
@ -721,7 +722,7 @@ void HistoryWidget::animatedScrollToY(int scrollTo, HistoryItem *attachTo) {
auto itemTop = _list->itemTop(attachTo); auto itemTop = _list->itemTop(attachTo);
auto scrollTop = _scroll->scrollTop(); auto scrollTop = _scroll->scrollTop();
if (itemTop < 0 && !_history->isEmpty()) { if (itemTop < 0 && !_history->isEmpty()) {
attachTo = _history->blocks.back()->items.back(); attachTo = _history->blocks.back()->messages.back()->data();
itemTop = _list->itemTop(attachTo); itemTop = _list->itemTop(attachTo);
} }
if (itemTop < 0 || (scrollTop == scrollTo)) { if (itemTop < 0 || (scrollTop == scrollTo)) {
@ -797,13 +798,13 @@ void HistoryWidget::adjustHighlightedMessageToMigrated() {
&& _migrated && _migrated
&& !_migrated->isEmpty() && !_migrated->isEmpty()
&& _migrated->loadedAtBottom() && _migrated->loadedAtBottom()
&& _migrated->blocks.back()->items.back()->isGroupMigrate() && _migrated->blocks.back()->messages.back()->data()->isGroupMigrate()
&& _list->historyTop() != _list->historyDrawTop()) { && _list->historyTop() != _list->historyDrawTop()) {
auto highlighted = App::histItemById( auto highlighted = App::histItemById(
_history->channelId(), _history->channelId(),
_highlightedMessageId); _highlightedMessageId);
if (highlighted && highlighted->isGroupMigrate()) { if (highlighted && highlighted->isGroupMigrate()) {
_highlightedMessageId = -_migrated->blocks.back()->items.back()->id; _highlightedMessageId = -_migrated->blocks.back()->messages.back()->id();
} }
} }
} }
@ -2726,12 +2727,12 @@ void HistoryWidget::checkReplyReturns() {
auto scrollTopMax = _scroll->scrollTopMax(); auto scrollTopMax = _scroll->scrollTopMax();
auto scrollHeight = _scroll->height(); auto scrollHeight = _scroll->height();
while (_replyReturn) { while (_replyReturn) {
auto below = (_replyReturn->detached() && _replyReturn->history() == _history && !_history->isEmpty() && _replyReturn->id < _history->blocks.back()->items.back()->id); auto below = (_replyReturn->detached() && _replyReturn->history() == _history && !_history->isEmpty() && _replyReturn->id < _history->blocks.back()->messages.back()->id());
if (!below) { if (!below) {
below = (_replyReturn->detached() && _replyReturn->history() == _migrated && !_history->isEmpty()); below = (_replyReturn->detached() && _replyReturn->history() == _migrated && !_history->isEmpty());
} }
if (!below) { if (!below) {
below = (_replyReturn->detached() && _migrated && _replyReturn->history() == _migrated && !_migrated->isEmpty() && _replyReturn->id < _migrated->blocks.back()->items.back()->id); below = (_replyReturn->detached() && _migrated && _replyReturn->history() == _migrated && !_migrated->isEmpty() && _replyReturn->id < _migrated->blocks.back()->messages.back()->id());
} }
if (!below && !_replyReturn->detached()) { if (!below && !_replyReturn->detached()) {
below = (scrollTop >= scrollTopMax) || (_list->itemTop(_replyReturn) < scrollTop + scrollHeight / 2); below = (scrollTop >= scrollTopMax) || (_list->itemTop(_replyReturn) < scrollTop + scrollHeight / 2);
@ -5521,8 +5522,8 @@ void HistoryWidget::onReplyToMessage() {
if (!to || to->id <= 0 || !_canSendMessages) return; if (!to || to->id <= 0 || !_canSendMessages) return;
if (to->history() == _migrated) { if (to->history() == _migrated) {
if (to->isGroupMigrate() && !_history->isEmpty() && _history->blocks.front()->items.front()->isGroupMigrate() && _history != _migrated) { if (to->isGroupMigrate() && !_history->isEmpty() && _history->blocks.front()->messages.front()->data()->isGroupMigrate() && _history != _migrated) {
App::contextItem(_history->blocks.front()->items.front()); App::contextItem(_history->blocks.front()->messages.front()->data());
onReplyToMessage(); onReplyToMessage();
App::contextItem(to); App::contextItem(to);
} else { } else {

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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 "history/history_item_components.h"
#include "history/view/history_view_message.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "chat_helpers/message_field.h" #include "chat_helpers/message_field.h"
#include "mainwindow.h" #include "mainwindow.h"
@ -59,29 +60,29 @@ void ListWidget::enumerateItems(Method method) {
ending, ending,
_visibleTop, _visibleTop,
[this](auto &elem, int top) { [this](auto &elem, int top) {
return this->itemTop(elem) + elem->height() <= top; return this->itemTop(elem) + elem->data()->height() <= top;
}) })
: std::upper_bound( : std::upper_bound(
beginning, beginning,
ending, ending,
_visibleBottom, _visibleBottom,
[this](int bottom, auto &elem) { [this](int bottom, auto &elem) {
return this->itemTop(elem) + elem->height() >= bottom; return this->itemTop(elem) + elem->data()->height() >= bottom;
}); });
auto wasEnd = (from == ending); auto wasEnd = (from == ending);
if (wasEnd) { if (wasEnd) {
--from; --from;
} }
if (TopToBottom) { if (TopToBottom) {
Assert(itemTop(from->get()) + from->get()->height() > _visibleTop); Assert(itemTop(from->get()) + from->get()->data()->height() > _visibleTop);
} else { } else {
Assert(itemTop(from->get()) < _visibleBottom); Assert(itemTop(from->get()) < _visibleBottom);
} }
while (true) { while (true) {
auto item = from->get(); auto view = from->get();
auto itemtop = itemTop(item); auto itemtop = itemTop(view);
auto itembottom = itemtop + item->height(); auto itembottom = itemtop + view->data()->height();
// Binary search should've skipped all the items that are above / below the visible area. // Binary search should've skipped all the items that are above / below the visible area.
if (TopToBottom) { if (TopToBottom) {
@ -90,7 +91,7 @@ void ListWidget::enumerateItems(Method method) {
Assert(itemtop < _visibleBottom); Assert(itemtop < _visibleBottom);
} }
if (!method(item, itemtop, itembottom)) { if (!method(view, itemtop, itembottom)) {
return; return;
} }
@ -124,9 +125,9 @@ void ListWidget::enumerateUserpics(Method method) {
// -1 means we didn't find an attached to next message yet. // -1 means we didn't find an attached to next message yet.
int lowestAttachedItemTop = -1; int lowestAttachedItemTop = -1;
auto userpicCallback = [this, &lowestAttachedItemTop, &method](HistoryItem *item, int itemtop, int itembottom) { auto userpicCallback = [this, &lowestAttachedItemTop, &method](Message *view, int itemtop, int itembottom) {
// Skip all service messages. // Skip all service messages.
auto message = item->toHistoryMessage(); auto message = view->data()->toHistoryMessage();
if (!message) return true; if (!message) return true;
if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) { if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) {
@ -170,7 +171,8 @@ void ListWidget::enumerateDates(Method method) {
// -1 means we didn't find a same-day with previous message yet. // -1 means we didn't find a same-day with previous message yet.
auto lowestInOneDayItemBottom = -1; auto lowestInOneDayItemBottom = -1;
auto dateCallback = [this, &lowestInOneDayItemBottom, &method](HistoryItem *item, int itemtop, int itembottom) { auto dateCallback = [this, &lowestInOneDayItemBottom, &method](Message *view, int itemtop, int itembottom) {
const auto item = view->data();
if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) { if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) {
lowestInOneDayItemBottom = itembottom - item->marginBottom(); lowestInOneDayItemBottom = itembottom - item->marginBottom();
} }
@ -213,20 +215,23 @@ ListWidget::ListWidget(
: RpWidget(parent) : RpWidget(parent)
, _delegate(delegate) , _delegate(delegate)
, _controller(controller) , _controller(controller)
, _context(_delegate->listContext())
, _scrollDateCheck([this] { scrollDateCheck(); }) { , _scrollDateCheck([this] { scrollDateCheck(); }) {
setMouseTracking(true); setMouseTracking(true);
_scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); }); _scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); });
Auth().data().itemRepaintRequest( Auth().data().itemRepaintRequest(
) | rpl::start_with_next([this](auto item) { ) | rpl::start_with_next([this](auto item) {
if (ranges::find(_items, item) != _items.end()) { if (const auto view = viewForItem(item)) {
repaintItem(item); repaintItem(view);
} }
}, lifetime()); }, lifetime());
subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); }); subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); });
subscribe(Auth().data().queryItemVisibility(), [this](const Data::Session::ItemVisibilityQuery &query) { subscribe(Auth().data().queryItemVisibility(), [this](const Data::Session::ItemVisibilityQuery &query) {
if (ranges::find(_items, query.item) != _items.end()) { if (const auto view = viewForItem(query.item)) {
auto top = itemTop(query.item); const auto top = itemTop(view);
if (top >= 0 && top + query.item->height() > _visibleTop && top < _visibleBottom) { if (top >= 0
&& top + query.item->height() > _visibleTop
&& top < _visibleBottom) {
*query.isVisible = true; *query.isVisible = true;
} }
} }
@ -252,7 +257,7 @@ void ListWidget::refreshRows() {
_items.reserve(_slice.ids.size()); _items.reserve(_slice.ids.size());
for (const auto &fullId : _slice.ids) { for (const auto &fullId : _slice.ids) {
if (const auto item = App::histItemById(fullId)) { if (const auto item = App::histItemById(fullId)) {
_items.push_back(item); _items.push_back(enforceViewForItem(item));
} }
} }
updateAroundPositionFromRows(); updateAroundPositionFromRows();
@ -274,8 +279,8 @@ void ListWidget::restoreScrollState() {
} }
const auto index = findNearestItem(_scrollTopState.item); const auto index = findNearestItem(_scrollTopState.item);
if (index >= 0) { if (index >= 0) {
const auto item = _items[index]; const auto view = _items[index];
auto newVisibleTop = itemTop(item) + _scrollTopState.shift; auto newVisibleTop = itemTop(view) + _scrollTopState.shift;
if (_visibleTop != newVisibleTop) { if (_visibleTop != newVisibleTop) {
_delegate->listScrollTo(newVisibleTop); _delegate->listScrollTo(newVisibleTop);
} }
@ -283,10 +288,30 @@ void ListWidget::restoreScrollState() {
_scrollTopState = ScrollTopState(); _scrollTopState = ScrollTopState();
} }
Message *ListWidget::viewForItem(const HistoryItem *item) const {
if (item) {
if (const auto i = _views.find(item); i != _views.end()) {
return i->second.get();
}
}
return nullptr;
}
not_null<Message*> ListWidget::enforceViewForItem(
not_null<HistoryItem*> item) {
if (const auto view = viewForItem(item)) {
return view;
}
const auto [i, ok] = _views.emplace(
item,
std::make_unique<Message>(item, _context));
return i->second.get();
}
void ListWidget::updateAroundPositionFromRows() { void ListWidget::updateAroundPositionFromRows() {
_aroundIndex = findNearestItem(_aroundPosition); _aroundIndex = findNearestItem(_aroundPosition);
if (_aroundIndex >= 0) { if (_aroundIndex >= 0) {
_aroundPosition = _items[_aroundIndex]->position(); _aroundPosition = _items[_aroundIndex]->data()->position();
} }
} }
@ -296,8 +321,8 @@ int ListWidget::findNearestItem(Data::MessagePosition position) const {
} }
const auto after = ranges::find_if( const auto after = ranges::find_if(
_items, _items,
[&](not_null<HistoryItem*> item) { [&](not_null<Message*> view) {
return (item->position() >= position); return (view->data()->position() >= position);
}); });
return (after == end(_items)) return (after == end(_items))
? int(_items.size() - 1) ? int(_items.size() - 1)
@ -410,10 +435,10 @@ void ListWidget::checkMoveToOtherViewer() {
- kPreloadIfLessThanScreens; - kPreloadIfLessThanScreens;
auto minUniversalIdDelta = (minScreenDelta * visibleHeight) auto minUniversalIdDelta = (minScreenDelta * visibleHeight)
/ minItemHeight; / minItemHeight;
auto preloadAroundItem = [&](not_null<HistoryItem*> item) { auto preloadAroundMessage = [&](not_null<Message*> view) {
auto preloadRequired = false; auto preloadRequired = false;
auto itemPosition = item->position(); auto itemPosition = view->data()->position();
auto itemIndex = ranges::find(_items, item) - begin(_items); auto itemIndex = ranges::find(_items, view) - begin(_items);
Assert(itemIndex < _items.size()); Assert(itemIndex < _items.size());
if (!preloadRequired) { if (!preloadRequired) {
@ -433,9 +458,9 @@ void ListWidget::checkMoveToOtherViewer() {
}; };
if (preloadTop && !topLoaded) { if (preloadTop && !topLoaded) {
preloadAroundItem(topItem); preloadAroundMessage(topItem);
} else if (preloadBottom && !bottomLoaded) { } else if (preloadBottom && !bottomLoaded) {
preloadAroundItem(bottomItem); preloadAroundMessage(bottomItem);
} }
} }
@ -491,9 +516,10 @@ void ListWidget::itemsAdded(Direction direction, int addedCount) {
: (addedCount + 1); : (addedCount + 1);
for (auto i = checkFrom; i != checkTo; ++i) { for (auto i = checkFrom; i != checkTo; ++i) {
if (i > 0) { if (i > 0) {
auto item = _items[i - 1].get(); const auto item = _items[i - 1]->data();
if (i < _items.size()) { if (i < _items.size()) {
auto previous = _items[i].get(); // #TODO feeds show
auto previous = _items[i]->data();
item->setLogEntryDisplayDate(item->date.date() != previous->date.date()); item->setLogEntryDisplayDate(item->date.date() != previous->date.date());
auto attachToPrevious = item->computeIsAttachToPrevious(previous); auto attachToPrevious = item->computeIsAttachToPrevious(previous);
item->setLogEntryAttachToPrevious(attachToPrevious); item->setLogEntryAttachToPrevious(attachToPrevious);
@ -518,7 +544,7 @@ int ListWidget::resizeGetHeight(int newWidth) {
auto newHeight = 0; auto newHeight = 0;
for (auto &item : _items) { for (auto &item : _items) {
item->setY(newHeight); item->setY(newHeight);
newHeight += item->resizeGetHeight(newWidth); newHeight += item->data()->resizeGetHeight(newWidth);
} }
_itemsHeight = newHeight; _itemsHeight = newHeight;
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0; _itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0;
@ -543,7 +569,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
auto clip = e->rect(); auto clip = e->rect();
auto from = std::lower_bound(begin(_items), end(_items), clip.top(), [this](auto &elem, int top) { auto from = std::lower_bound(begin(_items), end(_items), clip.top(), [this](auto &elem, int top) {
return this->itemTop(elem) + elem->height() <= top; return this->itemTop(elem) + elem->data()->height() <= top;
}); });
auto to = std::lower_bound(begin(_items), end(_items), clip.top() + clip.height(), [this](auto &elem, int bottom) { auto to = std::lower_bound(begin(_items), end(_items), clip.top() + clip.height(), [this](auto &elem, int bottom) {
return this->itemTop(elem) < bottom; return this->itemTop(elem) < bottom;
@ -553,8 +579,8 @@ void ListWidget::paintEvent(QPaintEvent *e) {
p.translate(0, top); p.translate(0, top);
for (auto i = from; i != to; ++i) { for (auto i = from; i != to; ++i) {
auto selection = (*i == _selectedItem) ? _selectedText : TextSelection(); auto selection = (*i == _selectedItem) ? _selectedText : TextSelection();
(*i)->draw(p, clip.translated(0, -top), selection, ms); (*i)->data()->draw(p, clip.translated(0, -top), selection, ms);
auto height = (*i)->height(); auto height = (*i)->data()->height();
top += height; top += height;
p.translate(0, height); p.translate(0, height);
} }
@ -616,27 +642,27 @@ void ListWidget::paintEvent(QPaintEvent *e) {
TextWithEntities ListWidget::getSelectedText() const { TextWithEntities ListWidget::getSelectedText() const {
return _selectedItem return _selectedItem
? _selectedItem->selectedText(_selectedText) ? _selectedItem->data()->selectedText(_selectedText)
: TextWithEntities(); : TextWithEntities();
} }
not_null<HistoryItem*> ListWidget::findItemByY(int y) const { not_null<Message*> ListWidget::findItemByY(int y) const {
Expects(!_items.empty()); Expects(!_items.empty());
if (y < _itemsTop) { if (y < _itemsTop) {
return _items.front().get(); return _items.front();
} }
auto i = std::lower_bound( auto i = std::lower_bound(
begin(_items), begin(_items),
end(_items), end(_items),
y, y,
[this](auto &elem, int top) { [this](auto &elem, int top) {
return this->itemTop(elem) + elem->height() <= top; return this->itemTop(elem) + elem->data()->height() <= top;
}); });
return (i != end(_items)) ? i->get() : _items.back().get(); return (i != end(_items)) ? i->get() : _items.back().get();
} }
HistoryItem *ListWidget::strictFindItemByY(int y) const { Message *ListWidget::strictFindItemByY(int y) const {
if (_items.empty()) { if (_items.empty()) {
return nullptr; return nullptr;
} }
@ -651,7 +677,7 @@ auto ListWidget::countScrollState() const -> ScrollTopState {
} }
auto topItem = findItemByY(_visibleTop); auto topItem = findItemByY(_visibleTop);
return { return {
topItem->position(), topItem->data()->position(),
_visibleTop - itemTop(topItem) _visibleTop - itemTop(topItem)
}; };
} }
@ -675,7 +701,7 @@ void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) {
if (((_mouseAction == MouseAction::Selecting && _selectedItem != nullptr) || (_mouseAction == MouseAction::None)) && _mouseSelectType == TextSelectType::Letters && _mouseActionItem) { if (((_mouseAction == MouseAction::Selecting && _selectedItem != nullptr) || (_mouseAction == MouseAction::None)) && _mouseSelectType == TextSelectType::Letters && _mouseActionItem) {
HistoryStateRequest request; HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol; request.flags |= Text::StateRequest::Flag::LookupSymbol;
auto dragState = _mouseActionItem->getState(_dragStartPosition, request); auto dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
if (dragState.cursor == HistoryInTextCursorState) { if (dragState.cursor == HistoryInTextCursorState) {
_mouseTextSymbol = dragState.symbol; _mouseTextSymbol = dragState.symbol;
_mouseSelectType = TextSelectType::Words; _mouseSelectType = TextSelectType::Words;
@ -716,7 +742,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
auto selTo = _selectedText.to; auto selTo = _selectedText.to;
hasSelected = (selTo > selFrom) ? 1 : 0; hasSelected = (selTo > selFrom) ? 1 : 0;
if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) { if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) {
auto mousePos = mapPointToItem(mapFromGlobal(_mousePosition), App::mousedItem()); auto mousePos = mapPointToItem(mapFromGlobal(_mousePosition), viewForItem(App::mousedItem()));
HistoryStateRequest request; HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol; request.flags |= Text::StateRequest::Flag::LookupSymbol;
auto dragState = App::mousedItem()->getState(mousePos, request); auto dragState = App::mousedItem()->getState(mousePos, request);
@ -971,7 +997,7 @@ void ListWidget::enterEventHook(QEvent *e) {
void ListWidget::leaveEventHook(QEvent *e) { void ListWidget::leaveEventHook(QEvent *e) {
if (auto item = App::hoveredItem()) { if (auto item = App::hoveredItem()) {
repaintItem(item); repaintItem(viewForItem(item));
App::hoveredItem(nullptr); App::hoveredItem(nullptr);
} }
ClickHandler::clearActive(); ClickHandler::clearActive();
@ -989,13 +1015,13 @@ void ListWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butto
ClickHandler::pressed(); ClickHandler::pressed();
if (App::pressedItem() != App::hoveredItem()) { if (App::pressedItem() != App::hoveredItem()) {
repaintItem(App::pressedItem()); repaintItem(viewForItem(App::pressedItem()));
App::pressedItem(App::hoveredItem()); App::pressedItem(App::hoveredItem());
repaintItem(App::pressedItem()); repaintItem(viewForItem(App::pressedItem()));
} }
_mouseAction = MouseAction::None; _mouseAction = MouseAction::None;
_mouseActionItem = App::mousedItem(); _mouseActionItem = viewForItem(App::mousedItem());
_dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), _mouseActionItem); _dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), _mouseActionItem);
_pressWasInactive = _controller->window()->wasInactivePress(); _pressWasInactive = _controller->window()->wasInactivePress();
if (_pressWasInactive) _controller->window()->setInactivePress(false); if (_pressWasInactive) _controller->window()->setInactivePress(false);
@ -1008,7 +1034,7 @@ void ListWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butto
if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) {
HistoryStateRequest request; HistoryStateRequest request;
request.flags = Text::StateRequest::Flag::LookupSymbol; request.flags = Text::StateRequest::Flag::LookupSymbol;
dragState = _mouseActionItem->getState(_dragStartPosition, request); dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
if (dragState.cursor == HistoryInTextCursorState) { if (dragState.cursor == HistoryInTextCursorState) {
auto selection = TextSelection { dragState.symbol, dragState.symbol }; auto selection = TextSelection { dragState.symbol, dragState.symbol };
repaintItem(std::exchange(_selectedItem, _mouseActionItem)); repaintItem(std::exchange(_selectedItem, _mouseActionItem));
@ -1022,7 +1048,7 @@ void ListWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butto
} else if (App::pressedItem()) { } else if (App::pressedItem()) {
HistoryStateRequest request; HistoryStateRequest request;
request.flags = Text::StateRequest::Flag::LookupSymbol; request.flags = Text::StateRequest::Flag::LookupSymbol;
dragState = _mouseActionItem->getState(_dragStartPosition, request); dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
} }
if (_mouseSelectType != TextSelectType::Paragraphs) { if (_mouseSelectType != TextSelectType::Paragraphs) {
if (App::pressedItem()) { if (App::pressedItem()) {
@ -1077,7 +1103,7 @@ void ListWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton butt
activated = nullptr; activated = nullptr;
} }
if (App::pressedItem()) { if (App::pressedItem()) {
repaintItem(App::pressedItem()); repaintItem(viewForItem(App::pressedItem()));
App::pressedItem(nullptr); App::pressedItem(nullptr);
} }
@ -1115,27 +1141,28 @@ void ListWidget::updateSelected() {
auto point = QPoint(snap(mousePosition.x(), 0, width()), snap(mousePosition.y(), _visibleTop, _visibleBottom)); auto point = QPoint(snap(mousePosition.x(), 0, width()), snap(mousePosition.y(), _visibleTop, _visibleBottom));
auto itemPoint = QPoint(); auto itemPoint = QPoint();
auto item = strictFindItemByY(point.y()); const auto view = strictFindItemByY(point.y());
if (item) { const auto item = view ? view->data().get() : nullptr;
if (view) {
App::mousedItem(item); App::mousedItem(item);
itemPoint = mapPointToItem(point, item); itemPoint = mapPointToItem(point, view);
if (item->hasPoint(itemPoint)) { if (item->hasPoint(itemPoint)) {
if (App::hoveredItem() != item) { if (App::hoveredItem() != item) {
repaintItem(App::hoveredItem()); repaintItem(viewForItem(App::hoveredItem()));
App::hoveredItem(item); App::hoveredItem(item);
repaintItem(App::hoveredItem()); repaintItem(view);
} }
} else if (App::hoveredItem()) { } else if (App::hoveredItem()) {
repaintItem(App::hoveredItem()); repaintItem(viewForItem(App::hoveredItem()));
App::hoveredItem(nullptr); App::hoveredItem(nullptr);
} }
} }
HistoryTextState dragState; HistoryTextState dragState;
ClickHandlerHost *lnkhost = nullptr; ClickHandlerHost *lnkhost = nullptr;
auto selectingText = (item == _mouseActionItem && item == App::hoveredItem() && _selectedItem); auto selectingText = (view == _mouseActionItem && item == App::hoveredItem() && _selectedItem);
if (item) { if (view) {
if (item != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { if (view != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
if (_mouseAction == MouseAction::PrepareDrag) { if (_mouseAction == MouseAction::PrepareDrag) {
_mouseAction = MouseAction::Dragging; _mouseAction = MouseAction::Dragging;
InvokeQueued(this, [this] { performDrag(); }); InvokeQueued(this, [this] { performDrag(); });
@ -1188,7 +1215,7 @@ void ListWidget::updateSelected() {
} else if (_mouseCursorState == HistoryInDateCursorState) { } else if (_mouseCursorState == HistoryInDateCursorState) {
// cursor = style::cur_cross; // cursor = style::cur_cross;
} }
} else if (item) { } else if (view) {
if (_mouseAction == MouseAction::Selecting) { if (_mouseAction == MouseAction::Selecting) {
if (selectingText) { if (selectingText) {
auto second = dragState.symbol; auto second = dragState.symbol;
@ -1197,7 +1224,7 @@ void ListWidget::updateSelected() {
} }
auto selection = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }; auto selection = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) };
if (_mouseSelectType != TextSelectType::Letters) { if (_mouseSelectType != TextSelectType::Letters) {
selection = _mouseActionItem->adjustSelection(selection, _mouseSelectType); selection = _mouseActionItem->data()->adjustSelection(selection, _mouseSelectType);
} }
if (_selectedText != selection) { if (_selectedText != selection) {
_selectedText = selection; _selectedText = selection;
@ -1331,24 +1358,24 @@ void ListWidget::performDrag() {
//} // #TODO drag //} // #TODO drag
} }
int ListWidget::itemTop(not_null<const HistoryItem*> item) const { int ListWidget::itemTop(not_null<const Message*> view) const {
return _itemsTop + item->y(); return _itemsTop + view->y();
} }
void ListWidget::repaintItem(const HistoryItem *item) { void ListWidget::repaintItem(const Message *view) {
if (!item) { if (!view) {
return; return;
} }
update(0, itemTop(item), width(), item->height()); update(0, itemTop(view), width(), view->data()->height());
} }
QPoint ListWidget::mapPointToItem( QPoint ListWidget::mapPointToItem(
QPoint point, QPoint point,
const HistoryItem *item) const { const Message *view) const {
if (!item) { if (!view) {
return QPoint(); return QPoint();
} }
return point - QPoint(0, itemTop(item)); return point - QPoint(0, itemTop(view));
} }
void ListWidget::handlePendingHistoryResize() { void ListWidget::handlePendingHistoryResize() {

View File

@ -23,8 +23,12 @@ class Controller;
namespace HistoryView { namespace HistoryView {
enum class Context : char;
class Message;
class ListDelegate { class ListDelegate {
public: public:
virtual Context listContext() = 0;
virtual void listScrollTo(int top) = 0; virtual void listScrollTo(int top) = 0;
virtual void listCloseRequest() = 0; virtual void listCloseRequest() = 0;
virtual rpl::producer<Data::MessagesSlice> listSource( virtual rpl::producer<Data::MessagesSlice> listSource(
@ -138,15 +142,18 @@ private:
void saveScrollState(); void saveScrollState();
void restoreScrollState(); void restoreScrollState();
Message *viewForItem(const HistoryItem *item) const;
not_null<Message*> enforceViewForItem(not_null<HistoryItem*> item);
void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button); void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
void mouseActionUpdate(const QPoint &screenPos); void mouseActionUpdate(const QPoint &screenPos);
void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button); void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button);
void mouseActionCancel(); void mouseActionCancel();
void updateSelected(); void updateSelected();
void performDrag(); void performDrag();
int itemTop(not_null<const HistoryItem*> item) const; int itemTop(not_null<const Message*> view) const;
void repaintItem(const HistoryItem *item); void repaintItem(const Message *view);
QPoint mapPointToItem(QPoint point, const HistoryItem *item) const; QPoint mapPointToItem(QPoint point, const Message *view) const;
void handlePendingHistoryResize(); void handlePendingHistoryResize();
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
@ -165,8 +172,8 @@ private:
const TextWithEntities &forClipboard, const TextWithEntities &forClipboard,
QClipboard::Mode mode = QClipboard::Clipboard); QClipboard::Mode mode = QClipboard::Clipboard);
not_null<HistoryItem*> findItemByY(int y) const; not_null<Message*> findItemByY(int y) const;
HistoryItem *strictFindItemByY(int y) const; Message *strictFindItemByY(int y) const;
int findNearestItem(Data::MessagePosition position) const; int findNearestItem(Data::MessagePosition position) const;
void checkMoveToOtherViewer(); void checkMoveToOtherViewer();
@ -184,7 +191,7 @@ private:
// This function finds all history items that are displayed and calls template method // This function finds all history items that are displayed and calls template method
// for each found message (in given direction) in the passed history with passed top offset. // for each found message (in given direction) in the passed history with passed top offset.
// //
// Method has "bool (*Method)(HistoryItem *item, int itemtop, int itembottom)" signature // Method has "bool (*Method)(Message *view, int itemtop, int itembottom)" signature
// if it returns false the enumeration stops immediately. // if it returns false the enumeration stops immediately.
template <EnumItemsDirection direction, typename Method> template <EnumItemsDirection direction, typename Method>
void enumerateItems(Method method); void enumerateItems(Method method);
@ -210,18 +217,19 @@ private:
not_null<ListDelegate*> _delegate; not_null<ListDelegate*> _delegate;
not_null<Window::Controller*> _controller; not_null<Window::Controller*> _controller;
Data::MessagePosition _aroundPosition; Data::MessagePosition _aroundPosition;
Context _context;
int _aroundIndex = -1; int _aroundIndex = -1;
int _idsLimit = kMinimalIdsLimit; int _idsLimit = kMinimalIdsLimit;
Data::MessagesSlice _slice; Data::MessagesSlice _slice;
std::vector<not_null<HistoryItem*>> _items; std::vector<not_null<Message*>> _items;
std::map<uint64, HistoryItem*> _itemsByIds; std::map<not_null<HistoryItem*>, std::unique_ptr<Message>, std::less<>> _views;
int _itemsTop = 0; int _itemsTop = 0;
int _itemsHeight = 0; int _itemsHeight = 0;
int _minHeight = 0; int _minHeight = 0;
int _visibleTop = 0; int _visibleTop = 0;
int _visibleBottom = 0; int _visibleBottom = 0;
HistoryItem *_visibleTopItem = nullptr; Message *_visibleTopItem = nullptr;
int _visibleTopFromItem = 0; int _visibleTopFromItem = 0;
ScrollTopState _scrollTopState; ScrollTopState _scrollTopState;
@ -229,19 +237,19 @@ private:
Animation _scrollDateOpacity; Animation _scrollDateOpacity;
SingleQueuedInvokation _scrollDateCheck; SingleQueuedInvokation _scrollDateCheck;
base::Timer _scrollDateHideTimer; base::Timer _scrollDateHideTimer;
HistoryItem *_scrollDateLastItem = nullptr; Message *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0; int _scrollDateLastItemTop = 0;
MouseAction _mouseAction = MouseAction::None; MouseAction _mouseAction = MouseAction::None;
TextSelectType _mouseSelectType = TextSelectType::Letters; TextSelectType _mouseSelectType = TextSelectType::Letters;
QPoint _dragStartPosition; QPoint _dragStartPosition;
QPoint _mousePosition; QPoint _mousePosition;
HistoryItem *_mouseActionItem = nullptr; Message *_mouseActionItem = nullptr;
HistoryCursorState _mouseCursorState = HistoryDefaultCursorState; HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
uint16 _mouseTextSymbol = 0; uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false; bool _pressWasInactive = false;
HistoryItem *_selectedItem = nullptr; Message *_selectedItem = nullptr;
TextSelection _selectedText; TextSelection _selectedText;
bool _wasSelectedText = false; // was some text selected in current drag action bool _wasSelectedText = false; // was some text selected in current drag action
Qt::CursorShape _cursor = style::cur_default; Qt::CursorShape _cursor = style::cur_default;

View File

@ -0,0 +1,25 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_message.h"
namespace HistoryView {
Message::Message(not_null<HistoryItem*> data, Context context)
: _data(data)
, _context(context) {
}
MsgId Message::id() const {
return _data->id;
}
not_null<HistoryItem*> Message::data() const {
return _data;
}
} // namespace HistoryView

View File

@ -0,0 +1,43 @@
/*
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
*/
#pragma once
class HistoryItem;
namespace HistoryView {
enum class Context : char {
History,
Feed,
AdminLog
};
class Message
: public RuntimeComposer
, public ClickHandlerHost {
public:
Message(not_null<HistoryItem*> data, Context context);
MsgId id() const;
not_null<HistoryItem*> data() const;
int y() const {
return _y;
}
void setY(int y) {
_y = y;
}
private:
const not_null<HistoryItem*> _data;
int _y = 0;
Context _context;
};
} // namespace HistoryView

View File

@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_message.h" #include "history/history_message.h"
#include "history/history_media.h" #include "history/history_media.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_message.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "lang/lang_cloud_manager.h" #include "lang/lang_cloud_manager.h"
#include "boxes/add_contact_box.h" #include "boxes/add_contact_box.h"
@ -1134,8 +1135,9 @@ void MainWidget::deleteAllFromUser(ChannelData *channel, UserData *from) {
QVector<MsgId> toDestroy; QVector<MsgId> toDestroy;
if (auto history = App::historyLoaded(channel->id)) { if (auto history = App::historyLoaded(channel->id)) {
for_const (auto block, history->blocks) { for (const auto &block : history->blocks) {
for_const (auto item, block->items) { for (const auto &message : block->messages) {
const auto item = message->data();
if (item->from() == from && item->canDelete()) { if (item->from() == from && item->canDelete()) {
toDestroy.push_back(item->id); toDestroy.push_back(item->id);
} }

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/main_window.h" #include "window/main_window.h"
#include "info/info_memento.h" #include "info/info_memento.h"
#include "history/view/history_view_message.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "styles/style_window.h" #include "styles/style_window.h"
@ -252,13 +253,13 @@ void Controller::showJumpToDate(not_null<PeerData*> peer, QDate requestedDate) {
auto currentPeerDate = [peer] { auto currentPeerDate = [peer] {
if (auto history = App::historyLoaded(peer)) { if (auto history = App::historyLoaded(peer)) {
if (history->scrollTopItem) { if (history->scrollTopItem) {
return history->scrollTopItem->date.date(); return history->scrollTopItem->data()->date.date();
} else if (history->loadedAtTop() && !history->isEmpty() && history->peer->migrateFrom()) { } else if (history->loadedAtTop() && !history->isEmpty() && history->peer->migrateFrom()) {
if (auto migrated = App::historyLoaded(history->peer->migrateFrom())) { if (auto migrated = App::historyLoaded(history->peer->migrateFrom())) {
if (migrated->scrollTopItem) { if (migrated->scrollTopItem) {
// We're up in the migrated history. // We're up in the migrated history.
// So current date is the date of first message here. // So current date is the date of first message here.
return history->blocks.front()->items.front()->date.date(); return history->blocks.front()->messages.front()->data()->date.date();
} }
} }
} else if (!history->chatsListDate().isNull()) { } else if (!history->chatsListDate().isNull()) {
@ -287,7 +288,7 @@ void Controller::showJumpToDate(not_null<PeerData*> peer, QDate requestedDate) {
if (auto history = App::historyLoaded(chat)) { if (auto history = App::historyLoaded(chat)) {
if (history->loadedAtTop()) { if (history->loadedAtTop()) {
if (!history->isEmpty()) { if (!history->isEmpty()) {
return history->blocks.front()->items.front()->date.date(); return history->blocks.front()->messages.front()->data()->date.date();
} }
} else { } else {
return startDate(); return startDate();
@ -297,7 +298,7 @@ void Controller::showJumpToDate(not_null<PeerData*> peer, QDate requestedDate) {
if (auto history = App::historyLoaded(peer)) { if (auto history = App::historyLoaded(peer)) {
if (history->loadedAtTop()) { if (history->loadedAtTop()) {
if (!history->isEmpty()) { if (!history->isEmpty()) {
return history->blocks.front()->items.front()->date.date(); return history->blocks.front()->messages.front()->data()->date.date();
} }
return QDate::currentDate(); return QDate::currentDate();
} }

View File

@ -227,6 +227,8 @@
<(src_loc)/history/feed/history_feed_section.h <(src_loc)/history/feed/history_feed_section.h
<(src_loc)/history/view/history_view_list_widget.cpp <(src_loc)/history/view/history_view_list_widget.cpp
<(src_loc)/history/view/history_view_list_widget.h <(src_loc)/history/view/history_view_list_widget.h
<(src_loc)/history/view/history_view_message.cpp
<(src_loc)/history/view/history_view_message.h
<(src_loc)/history/view/history_view_service_message.cpp <(src_loc)/history/view/history_view_service_message.cpp
<(src_loc)/history/view/history_view_service_message.h <(src_loc)/history/view/history_view_service_message.h
<(src_loc)/history/view/history_view_top_bar_widget.cpp <(src_loc)/history/view/history_view_top_bar_widget.cpp