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_filter.h"
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_message.h"
#include "chat_helpers/message_field.h"
#include "mainwindow.h"
#include "mainwidget.h"
@ -54,16 +55,16 @@ void InnerWidget::enumerateItems(Method method) {
auto begin = std::rbegin(_items), end = std::rend(_items);
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) {
return this->itemTop(elem) + elem->height() >= bottom;
return this->itemTop(elem) + elem->data()->height() >= bottom;
});
auto wasEnd = (from == end);
if (wasEnd) {
--from;
}
if (TopToBottom) {
Assert(itemTop(from->get()) + from->get()->height() > _visibleTop);
Assert(itemTop(from->get()) + from->get()->data()->height() > _visibleTop);
} else {
Assert(itemTop(from->get()) < _visibleBottom);
}
@ -71,7 +72,7 @@ void InnerWidget::enumerateItems(Method method) {
while (true) {
auto item = from->get();
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.
if (TopToBottom) {
@ -114,8 +115,9 @@ void InnerWidget::enumerateUserpics(Method method) {
// -1 means we didn't find an attached to next message yet.
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.
const auto item = view->data();
auto message = item->toHistoryMessage();
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.
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()) {
lowestInOneDayItemBottom = itembottom - item->marginBottom();
}
@ -211,7 +214,7 @@ InnerWidget::InnerWidget(
Auth().data().itemRepaintRequest(
) | rpl::start_with_next([this](auto item) {
if (item->isLogEntry() && _history == item->history()) {
repaintItem(item);
repaintItem(viewForItem(item));
}
}, lifetime());
subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); });
@ -219,9 +222,11 @@ InnerWidget::InnerWidget(
if (_history != query.item->history() || !query.item->isLogEntry() || !isVisible()) {
return;
}
auto top = itemTop(query.item);
if (top >= 0 && top + query.item->height() > _visibleTop && top < _visibleBottom) {
*query.isVisible = true;
if (const auto view = viewForItem(query.item)) {
auto top = itemTop(view);
if (top >= 0 && top + query.item->height() > _visibleTop && top < _visibleBottom) {
*query.isVisible = true;
}
}
});
updateEmptyText();
@ -252,7 +257,7 @@ void InnerWidget::updateVisibleTopItem() {
} else {
auto begin = std::rbegin(_items), end = std::rend(_items);
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) {
_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 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 &addToItems = (direction == Direction::Up) ? _items : newItemsForDownDirection;
addToItems.reserve(oldItemsCount + events.size() * 2);
@ -528,8 +533,9 @@ void InnerWidget::addEvents(Direction direction, const QVector<MTPChannelAdminLo
}
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());
_itemsByData.emplace(item->data(), item.get());
addToItems.push_back(std::move(item));
++count;
});
@ -575,9 +581,9 @@ void InnerWidget::itemsAdded(Direction direction, int addedCount) {
auto checkTo = (direction == Direction::Up) ? (_items.size() + 1) : (addedCount + 1);
for (auto i = checkFrom; i != checkTo; ++i) {
if (i > 0) {
auto item = _items[i - 1].get();
const auto item = _items[i - 1]->data();
if (i < _items.size()) {
auto previous = _items[i].get();
const auto previous = _items[i]->data();
item->setLogEntryDisplayDate(item->date.date() != previous->date.date());
auto attachToPrevious = item->computeIsAttachToPrevious(previous);
item->setLogEntryAttachToPrevious(attachToPrevious);
@ -603,7 +609,7 @@ int InnerWidget::resizeGetHeight(int newWidth) {
auto newHeight = 0;
for (auto &item : base::reversed(_items)) {
item->setY(newHeight);
newHeight += item->resizeGetHeight(newWidth);
newHeight += item->data()->resizeGetHeight(newWidth);
}
_itemsHeight = newHeight;
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0;
@ -611,7 +617,9 @@ int InnerWidget::resizeGetHeight(int newWidth) {
}
void InnerWidget::restoreScrollPosition() {
auto newVisibleTop = _visibleTopItem ? (itemTop(_visibleTopItem) + _visibleTopFromItem) : ScrollMax;
auto newVisibleTop = _visibleTopItem
? (itemTop(_visibleTopItem) + _visibleTopFromItem)
: ScrollMax;
scrollToSignal.notify(newVisibleTop, true);
}
@ -630,7 +638,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
} else {
auto begin = std::rbegin(_items), end = std::rend(_items);
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) {
return this->itemTop(elem) < bottom;
@ -639,15 +647,19 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
auto top = itemTop(from->get());
p.translate(0, top);
for (auto i = from; i != to; ++i) {
auto selection = (*i == _selectedItem) ? _selectedText : TextSelection();
(*i)->draw(p, clip.translated(0, -top), selection, ms);
auto height = (*i)->height();
const auto view = i->get();
const auto selection = (view == _selectedItem)
? _selectedText
: TextSelection();
const auto item = view->data();
item->draw(p, clip.translated(0, -top), selection, ms);
auto height = item->height();
top += height;
p.translate(0, height);
}
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
if (userpicTop >= clip.top() + clip.height()) {
return false;
@ -662,7 +674,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
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
if (dateTop + dateHeight <= clip.top()) {
return false;
@ -721,6 +733,16 @@ void InnerWidget::clearAfterFilterChange() {
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) {
style::font font(st::msgServiceFont);
auto rectWidth = st::historyAdminLogEmptyWidth;
@ -739,7 +761,9 @@ void InnerWidget::paintEmpty(Painter &p) {
}
TextWithEntities InnerWidget::getSelectedText() const {
return _selectedItem ? _selectedItem->selectedText(_selectedText) : TextWithEntities();
return _selectedItem
? _selectedItem->data()->selectedText(_selectedText)
: TextWithEntities();
}
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) {
HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol;
auto dragState = _mouseActionItem->getState(_dragStartPosition, request);
auto dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
if (dragState.cursor == HistoryInTextCursorState) {
_mouseTextSymbol = dragState.symbol;
_mouseSelectType = TextSelectType::Words;
@ -802,7 +826,9 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
auto selTo = _selectedText.to;
hasSelected = (selTo > selFrom) ? 1 : 0;
if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) {
auto mousePos = mapPointToItem(mapFromGlobal(_mousePosition), App::mousedItem());
auto mousePos = mapPointToItem(
mapFromGlobal(_mousePosition),
viewForItem(App::mousedItem()));
HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol;
auto dragState = App::mousedItem()->getState(mousePos, request);
@ -1137,7 +1163,7 @@ void InnerWidget::enterEventHook(QEvent *e) {
void InnerWidget::leaveEventHook(QEvent *e) {
if (auto item = App::hoveredItem()) {
repaintItem(item);
repaintItem(viewForItem(item));
App::hoveredItem(nullptr);
}
ClickHandler::clearActive();
@ -1155,14 +1181,16 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
ClickHandler::pressed();
if (App::pressedItem() != App::hoveredItem()) {
repaintItem(App::pressedItem());
repaintItem(viewForItem(App::pressedItem()));
App::pressedItem(App::hoveredItem());
repaintItem(App::pressedItem());
repaintItem(viewForItem(App::pressedItem()));
}
_mouseAction = MouseAction::None;
_mouseActionItem = App::mousedItem();
_dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), _mouseActionItem);
_mouseActionItem = viewForItem(App::mousedItem());
_dragStartPosition = mapPointToItem(
mapFromGlobal(screenPos),
_mouseActionItem);
_pressWasInactive = _controller->window()->wasInactivePress();
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()) {
HistoryStateRequest request;
request.flags = Text::StateRequest::Flag::LookupSymbol;
dragState = _mouseActionItem->getState(_dragStartPosition, request);
dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
if (dragState.cursor == HistoryInTextCursorState) {
auto selection = TextSelection { dragState.symbol, dragState.symbol };
repaintItem(std::exchange(_selectedItem, _mouseActionItem));
@ -1188,7 +1216,7 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
} else if (App::pressedItem()) {
HistoryStateRequest request;
request.flags = Text::StateRequest::Flag::LookupSymbol;
dragState = _mouseActionItem->getState(_dragStartPosition, request);
dragState = _mouseActionItem->data()->getState(_dragStartPosition, request);
}
if (_mouseSelectType != TextSelectType::Paragraphs) {
if (App::pressedItem()) {
@ -1243,7 +1271,7 @@ void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton but
activated = nullptr;
}
if (App::pressedItem()) {
repaintItem(App::pressedItem());
repaintItem(viewForItem(App::pressedItem()));
App::pressedItem(nullptr);
}
@ -1284,30 +1312,31 @@ void InnerWidget::updateSelected() {
auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = (point.y() >= _itemsTop && point.y() < _itemsTop + _itemsHeight)
? 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;
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) {
App::mousedItem(item);
itemPoint = mapPointToItem(point, item);
itemPoint = mapPointToItem(point, view);
if (item->hasPoint(itemPoint)) {
if (App::hoveredItem() != item) {
repaintItem(App::hoveredItem());
repaintItem(viewForItem(App::hoveredItem()));
App::hoveredItem(item);
repaintItem(App::hoveredItem());
repaintItem(view);
}
} else if (App::hoveredItem()) {
repaintItem(App::hoveredItem());
repaintItem(viewForItem(App::hoveredItem()));
App::hoveredItem(nullptr);
}
}
HistoryTextState dragState;
ClickHandlerHost *lnkhost = nullptr;
auto selectingText = (item == _mouseActionItem && item == App::hoveredItem() && _selectedItem);
if (item) {
if (item != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
auto selectingText = (view == _mouseActionItem && item == App::hoveredItem() && _selectedItem);
if (view) {
if (view != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
if (_mouseAction == MouseAction::PrepareDrag) {
_mouseAction = MouseAction::Dragging;
InvokeQueued(this, [this] { performDrag(); });
@ -1369,7 +1398,9 @@ void InnerWidget::updateSelected() {
}
auto selection = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) };
if (_mouseSelectType != TextSelectType::Letters) {
selection = _mouseActionItem->adjustSelection(selection, _mouseSelectType);
selection = _mouseActionItem->data()->adjustSelection(
selection,
_mouseSelectType);
}
if (_selectedText != selection) {
_selectedText = selection;
@ -1394,7 +1425,7 @@ void InnerWidget::updateSelected() {
if (auto pressedItem = App::pressedLinkItem()) {
if (!pressedItem->detached()) {
if (pressedItem->history() == _history) {
auto adjustedPoint = mapPointToItem(point, pressedItem);
auto adjustedPoint = mapPointToItem(point, viewForItem(pressedItem));
pressedItem->updatePressed(adjustedPoint);
}
}
@ -1502,22 +1533,22 @@ void InnerWidget::performDrag() {
//} // TODO
}
int InnerWidget::itemTop(not_null<const HistoryItem*> item) const {
int InnerWidget::itemTop(not_null<const Message*> item) const {
return _itemsTop + item->y();
}
void InnerWidget::repaintItem(const HistoryItem *item) {
void InnerWidget::repaintItem(const Message *item) {
if (!item) {
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 {
if (!item) {
QPoint InnerWidget::mapPointToItem(QPoint point, const Message *view) const {
if (!view) {
return QPoint();
}
return point - QPoint(0, itemTop(item));
return point - QPoint(0, itemTop(view));
}
void InnerWidget::handlePendingHistoryResize() {

View File

@ -22,6 +22,10 @@ namespace Window {
class Controller;
} // namespace Window
namespace HistoryView {
class Message;
} // namespace HistoryView
namespace AdminLog {
class SectionMemento;
@ -86,6 +90,7 @@ protected:
int resizeGetHeight(int newWidth) override;
private:
using Message = HistoryView::Message;
enum class Direction {
Up,
Down,
@ -107,9 +112,9 @@ private:
void mouseActionCancel();
void updateSelected();
void performDrag();
int itemTop(not_null<const HistoryItem*> item) const;
void repaintItem(const HistoryItem *item);
QPoint mapPointToItem(QPoint point, const HistoryItem *item) const;
int itemTop(not_null<const Message*> item) const;
void repaintItem(const Message *item);
QPoint mapPointToItem(QPoint point, const Message *item) const;
void handlePendingHistoryResize();
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
@ -141,6 +146,7 @@ private:
void clearAfterFilterChange();
void clearAndRequestLog();
void addEvents(Direction direction, const QVector<MTPChannelAdminLogEvent> &events);
Message *viewForItem(const HistoryItem *item);
void toggleScrollDateShown();
void repaintScrollDateCallback();
@ -152,7 +158,7 @@ private:
// 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.
//
// 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.
template <EnumItemsDirection direction, typename Method>
void enumerateItems(Method method);
@ -176,8 +182,9 @@ private:
not_null<Window::Controller*> _controller;
not_null<ChannelData*> _channel;
not_null<History*> _history;
std::vector<HistoryItemOwned> _items;
std::map<uint64, HistoryItem*> _itemsByIds;
std::vector<OwnedItem> _items;
std::map<uint64, not_null<Message*>> _itemsByIds;
std::map<not_null<HistoryItem*>, not_null<Message*>, std::less<>> _itemsByData;
int _itemsTop = 0;
int _itemsHeight = 0;
@ -185,14 +192,14 @@ private:
int _minHeight = 0;
int _visibleTop = 0;
int _visibleBottom = 0;
HistoryItem *_visibleTopItem = nullptr;
Message *_visibleTopItem = nullptr;
int _visibleTopFromItem = 0;
bool _scrollDateShown = false;
Animation _scrollDateOpacity;
SingleQueuedInvokation _scrollDateCheck;
base::Timer _scrollDateHideTimer;
HistoryItem *_scrollDateLastItem = nullptr;
Message *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0;
// Up - max, Down - min.
@ -211,12 +218,12 @@ private:
TextSelectType _mouseSelectType = TextSelectType::Letters;
QPoint _dragStartPosition;
QPoint _mousePosition;
HistoryItem *_mouseActionItem = nullptr;
Message *_mouseActionItem = nullptr;
HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false;
HistoryItem *_selectedItem = nullptr;
Message *_selectedItem = nullptr;
TextSelection _selectedText;
bool _wasSelectedText = false; // was some text selected in current drag action
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_inner.h"
#include "history/view/history_view_message.h"
#include "history/history_service.h"
#include "history/history_message.h"
#include "history/admin_log/history_admin_log_inner.h"
#include "lang/lang_keys.h"
#include "boxes/sticker_set_box.h"
#include "core/tl_help.h"
@ -281,7 +282,32 @@ TextWithEntities GenerateParticipantChangeText(not_null<ChannelData*> channel, c
} // 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());
auto id = event.vid.v;
@ -290,7 +316,7 @@ void GenerateItems(not_null<History*> history, LocalIdManager &idManager, const
auto &action = event.vaction;
auto date = event.vdate;
auto addPart = [&callback](not_null<HistoryItem*> item) {
return callback(HistoryItemOwned(item));
return callback(OwnedItem(item));
};
using Flag = MTPDmessage::Flag;

View File

@ -9,42 +9,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace AdminLog {
class HistoryItemOwned;
class OwnedItem;
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.
class HistoryItemOwned {
class OwnedItem {
public:
explicit HistoryItemOwned(not_null<HistoryItem*> data) : _data(data) {
}
HistoryItemOwned(const HistoryItemOwned &other) = delete;
HistoryItemOwned &operator=(const HistoryItemOwned &other) = delete;
HistoryItemOwned(HistoryItemOwned &&other) : _data(base::take(other._data)) {
}
HistoryItemOwned &operator=(HistoryItemOwned &&other) {
_data = base::take(other._data);
return *this;
}
~HistoryItemOwned() {
if (_data) {
_data->destroy();
}
}
explicit OwnedItem(not_null<HistoryItem*> data);
OwnedItem(const OwnedItem &other) = delete;
OwnedItem &operator=(const OwnedItem &other) = delete;
OwnedItem(OwnedItem &&other);
OwnedItem &operator=(OwnedItem &&other);
~OwnedItem();
HistoryItem *get() const {
return _data;
HistoryView::Message *get() const {
return _view.get();
}
HistoryItem *operator->() const {
HistoryView::Message *operator->() const {
return get();
}
operator HistoryItem*() const {
operator HistoryView::Message*() const {
return get();
}
private:
HistoryItem *_data = nullptr;
std::unique_ptr<HistoryView::Message> _view;
};

View File

@ -123,6 +123,8 @@ private:
class SectionMemento : public Window::SectionMemento {
public:
using Message = HistoryView::Message;
SectionMemento(not_null<ChannelData*> channel) : _channel(channel) {
}
@ -155,7 +157,11 @@ public:
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);
_itemsByIds = std::move(itemsByIds);
_upLoaded = upLoaded;
@ -170,10 +176,10 @@ public:
void setIdManager(LocalIdManager &&manager) {
_idManager = std::move(manager);
}
std::vector<HistoryItemOwned> takeItems() {
std::vector<OwnedItem> takeItems() {
return std::move(_items);
}
std::map<uint64, HistoryItem*> takeItemsByIds() {
std::map<uint64, not_null<Message*>> takeItemsByIds() {
return std::move(_itemsByIds);
}
LocalIdManager takeIdManager() {
@ -197,8 +203,8 @@ private:
int _scrollTop = 0;
std::vector<not_null<UserData*>> _admins;
std::vector<not_null<UserData*>> _adminsCanEdit;
std::vector<HistoryItemOwned> _items;
std::map<uint64, HistoryItem*> _itemsByIds;
std::vector<OwnedItem> _items;
std::map<uint64, not_null<Message*>> _itemsByIds;
bool _upLoaded = false;
bool _downLoaded = true;
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_list_widget.h"
#include "history/view/history_view_message.h"
#include "lang/lang_keys.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
@ -135,6 +136,10 @@ bool Widget::cmd_search() {
return true;
}
HistoryView::Context Widget::listContext() {
return HistoryView::Context::Feed;
}
void Widget::listScrollTo(int top) {
if (_scroll->scrollTop() != top) {
_scroll->scrollToY(top);

View File

@ -59,6 +59,7 @@ public:
bool cmd_search() override;
// HistoryView::ListDelegate interface.
HistoryView::Context listContext() override;
void listScrollTo(int top) override;
void listCloseRequest() override;
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/view/history_view_message.h"
#include "history/history_message.h"
#include "history/history_media_types.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) {
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();
} else {
auto existing = cloudDraft();
@ -369,11 +374,11 @@ void ChannelHistory::getRangeDifference() {
auto fromId = MsgId(0);
auto toId = MsgId(0);
for (auto blockIndex = 0, blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) {
auto block = blocks[blockIndex];
for (auto itemIndex = 0, itemsCount = int(block->items.size()); itemIndex < itemsCount; ++itemIndex) {
auto item = block->items[itemIndex];
if (item->id > 0) {
fromId = item->id;
const auto &block = blocks[blockIndex];
for (auto itemIndex = 0, itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) {
const auto &message = block->messages[itemIndex];
if (message->id() > 0) {
fromId = message->id();
break;
}
}
@ -382,11 +387,11 @@ void ChannelHistory::getRangeDifference() {
if (!fromId) return;
for (auto blockIndex = blocks.size(); blockIndex > 0;) {
auto block = blocks[--blockIndex];
for (auto itemIndex = block->items.size(); itemIndex > 0;) {
auto item = block->items[--itemIndex];
if (item->id > 0) {
toId = item->id;
const auto &block = blocks[--blockIndex];
for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
const auto &message = block->messages[--itemIndex];
if (message->id() > 0) {
toId = message->id();
break;
}
}
@ -438,9 +443,9 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) {
}
for (auto blockIndex = blocks.size(); blockIndex > 0;) {
auto block = blocks[--blockIndex];
for (auto itemIndex = block->items.size(); itemIndex > 0;) {
auto item = block->items[--itemIndex];
const auto &block = blocks[--blockIndex];
for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
const auto item = block->messages[--itemIndex]->data();
// Due to a server bug sometimes inviteDate is less (before) than the
// 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 firstDate, lastDate;
if (!blocks.empty()) {
firstDate = blocks.front()->items.front()->date;
lastDate = blocks.back()->items.back()->date;
firstDate = blocks.front()->messages.front()->data()->date;
lastDate = blocks.back()->messages.back()->data()->date;
}
if (!firstDate.isNull() && !lastDate.isNull() && (firstDate <= inviteDate || loadedAtTop()) && (lastDate > inviteDate || loadedAtBottom())) {
bool willBeLastMsg = (inviteDate >= lastDate);
@ -514,9 +519,9 @@ void ChannelHistory::checkMaxReadMessageDate() {
if (_maxReadMessageDate.isValid()) return;
for (auto blockIndex = blocks.size(); blockIndex > 0;) {
auto block = blocks[--blockIndex];
for (auto itemIndex = block->items.size(); itemIndex > 0;) {
auto item = block->items[--itemIndex];
const auto &block = blocks[--blockIndex];
for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
const auto item = block->messages[--itemIndex]->data();
if (!item->unread()) {
_maxReadMessageDate = item->date;
if (item->isGroupMigrate() && isMegagroup() && peer->migrateFrom()) {
@ -1339,29 +1344,26 @@ HistoryBlock *History::prepareBlockForAddingItem() {
return _buildingFrontBlock->block;
}
auto result = _buildingFrontBlock->block = new HistoryBlock(this);
if (_buildingFrontBlock->expectedItemsCount > 0) {
result->items.reserve(_buildingFrontBlock->expectedItemsCount + 1);
}
result->setIndexInHistory(0);
blocks.push_front(result);
for (int i = 1, l = blocks.size(); i < l; ++i) {
blocks.push_front(std::make_unique<HistoryBlock>(this));
for (auto i = 0, l = int(blocks.size()); i != l; ++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);
if (!addNewBlock) {
return blocks.back();
const auto addNewBlock = blocks.empty()
|| (blocks.back()->messages.size() >= kNewBlockEachMessage);
if (addNewBlock) {
blocks.push_back(std::make_unique<HistoryBlock>(this));
blocks.back()->setIndexInHistory(blocks.size() - 1);
blocks.back()->messages.reserve(kNewBlockEachMessage);
}
auto result = new HistoryBlock(this);
result->setIndexInHistory(blocks.size());
blocks.push_back(result);
result->items.reserve(kNewBlockEachMessage);
return result;
return blocks.back().get();
};
void History::addItemToBlock(not_null<HistoryItem*> item) {
@ -1369,8 +1371,10 @@ void History::addItemToBlock(not_null<HistoryItem*> item) {
auto block = prepareBlockForAddingItem();
item->attachToBlock(block, block->items.size());
block->items.push_back(item);
block->messages.push_back(std::make_unique<HistoryView::Message>(
item,
HistoryView::Context::History));
item->attachToBlock(block, block->messages.size() - 1);
item->previousItemChanged();
if (isBuildingFrontBlock() && _buildingFrontBlock->expectedItemsCount > 0) {
@ -1379,62 +1383,11 @@ void History::addItemToBlock(not_null<HistoryItem*> item) {
}
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 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) {
if (force || !medias[i].empty()) {
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.
markupSenders = &peer->asChannel()->mgInfo->markupSenders;
}
for (auto i = block->items.size(); i > 0; --i) {
auto item = block->items[i - 1];
for (auto i = block->messages.size(); i > 0; --i) {
const auto item = block->messages[i - 1]->data();
item->addToUnreadMentions(UnreadMentionType::Existing);
if (item->from()->id) {
if (lastAuthors) { // chats
@ -1678,8 +1631,9 @@ void History::checkAddAllToUnreadMentions() {
return;
}
for (const auto block : blocks) {
for (const auto item : block->items) {
for (const auto &block : blocks) {
for (const auto &message : block->messages) {
const auto item = message->data();
item->addToUnreadMentions(UnreadMentionType::Existing);
}
}
@ -1688,13 +1642,14 @@ void History::checkAddAllToUnreadMentions() {
void History::addBlockToSharedMedia(HistoryBlock *block) {
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
if (block) {
for (auto item : block->items) {
for (const auto &message : block->messages) {
const auto item = message->data();
if (auto types = item->sharedMediaTypes()) {
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
auto type = static_cast<Storage::SharedMediaType>(i);
if (types.test(type)) {
if (medias[i].empty()) {
medias[i].reserve(block->items.size());
medias[i].reserve(block->messages.size());
}
medias[i].push_back(item->id);
}
@ -1709,11 +1664,13 @@ int History::countUnread(MsgId upTo) {
int result = 0;
for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) {
--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;
if ((*j)->id > 0 && (*j)->id <= upTo) {
const auto item = (*j)->data();
if (item->id > 0 && item->id <= upTo) {
break;
} else if (!(*j)->out() && (*j)->unread() && (*j)->id > upTo) {
} else if (!item->out() && item->unread() && item->id > upTo) {
++result;
}
}
@ -1726,11 +1683,13 @@ void History::updateShowFrom() {
for (auto i = blocks.cend(); i != blocks.cbegin();) {
--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;
if ((*j)->id > 0 && (!(*j)->out() || !showFrom)) {
if ((*j)->id >= inboxReadBefore) {
showFrom = *j;
const auto item = (*j)->data();
if (item->id > 0 && (!item->out() || !showFrom)) {
if (item->id >= inboxReadBefore) {
showFrom = item;
} else {
return;
}
@ -1779,7 +1738,7 @@ MsgId History::outboxRead(HistoryItem *wasRead) {
}
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) {
@ -1839,21 +1798,28 @@ bool History::changeMute(bool newMute) {
}
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) {
auto l = block->items.size();
auto l = block->messages.size();
for (++i; i < l; ++i) {
if (block->items[i]->id > 0) {
showFrom = block->items[i];
const auto &message = block->messages[i];
if (setFromMessage(block->messages[i])) {
return;
}
}
}
for (auto j = block->indexInHistory() + 1, s = int(blocks.size()); j < s; ++j) {
block = blocks[j];
for_const (auto item, block->items) {
if (item->id > 0) {
showFrom = item;
block = blocks[j].get();
for (const auto &message : block->messages) {
if (setFromMessage(message)) {
return;
}
}
@ -1874,7 +1840,7 @@ QDateTime History::adjustChatListDate() const {
void History::countScrollState(int top) {
countScrollTopItem(top);
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;
}
int itemIndex = 0, blockIndex = 0, itemTop = 0;
if (scrollTopItem && !scrollTopItem->detached()) {
itemIndex = scrollTopItem->indexInBlock();
blockIndex = scrollTopItem->block()->indexInHistory();
auto itemIndex = 0;
auto blockIndex = 0;
auto itemTop = 0;
if (scrollTopItem) {
itemIndex = scrollTopItem->data()->indexInBlock();
blockIndex = scrollTopItem->data()->block()->indexInHistory();
itemTop = blocks[blockIndex]->y() + scrollTopItem->y();
}
if (itemTop > top) {
// go backward through history while we don't find an item that starts above
do {
auto block = blocks[blockIndex];
const auto &block = blocks[blockIndex];
for (--itemIndex; itemIndex >= 0; --itemIndex) {
auto item = block->items[itemIndex];
itemTop = block->y() + item->y();
const auto view = block->messages[itemIndex].get();
itemTop = block->y() + view->y();
if (itemTop <= top) {
scrollTopItem = item;
scrollTopItem = view;
return;
}
}
if (--blockIndex >= 0) {
itemIndex = blocks[blockIndex]->items.size();
itemIndex = blocks[blockIndex]->messages.size();
} else {
break;
}
} while (true);
scrollTopItem = blocks.front()->items.front();
scrollTopItem = blocks.front()->messages.front().get();
} else {
// go forward through history while we don't find the last item that starts above
for (int blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) {
auto block = blocks[blockIndex];
for (int itemsCount = block->items.size(); itemIndex < itemsCount; ++itemIndex) {
auto item = block->items[itemIndex];
itemTop = block->y() + item->y();
for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) {
const auto &block = blocks[blockIndex];
for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) {
itemTop = block->y() + block->messages[itemIndex]->y();
if (itemTop > top) {
Assert(itemIndex > 0 || blockIndex > 0);
if (itemIndex > 0) {
scrollTopItem = block->items[itemIndex - 1];
scrollTopItem = block->messages[itemIndex - 1].get();
} else {
scrollTopItem = blocks[blockIndex - 1]->items.back();
scrollTopItem = blocks[blockIndex - 1]->messages.back().get();
}
return;
}
}
itemIndex = 0;
}
scrollTopItem = blocks.back()->items.back();
scrollTopItem = blocks.back()->messages.back().get();
}
}
void History::getNextScrollTopItem(HistoryBlock *block, int32 i) {
++i;
if (i > 0 && i < block->items.size()) {
scrollTopItem = block->items[i];
if (i > 0 && i < block->messages.size()) {
scrollTopItem = block->messages[i].get();
return;
}
int j = block->indexInHistory() + 1;
if (j > 0 && j < blocks.size()) {
scrollTopItem = blocks[j]->items.front();
scrollTopItem = blocks[j]->messages.front().get();
return;
}
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 < blocks.size());
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->items.insert(block->items.begin() + itemIndex, newItem);
block->messages.insert(
block->messages.begin() + itemIndex,
std::make_unique<HistoryView::Message>(newItem, HistoryView::Context::History));
newItem->attachToBlock(block.get(), itemIndex);
newItem->previousItemChanged();
if (itemIndex + 1 < block->items.size()) {
for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) {
block->items[i]->setIndexInBlock(i);
if (itemIndex + 1 < block->messages.size()) {
for (auto i = itemIndex + 1, l = int(block->messages.size()); i != l; ++i) {
block->messages[i]->data()->setIndexInBlock(i);
}
block->items[itemIndex + 1]->previousItemChanged();
} else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->items.empty()) {
blocks[blockIndex + 1]->items.front()->previousItemChanged();
block->messages[itemIndex + 1]->data()->previousItemChanged();
} else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->messages.empty()) {
blocks[blockIndex + 1]->messages.front()->data()->previousItemChanged();
} else {
newItem->nextItemChanged();
}
@ -2001,10 +1973,10 @@ HistoryItem *History::findNextItem(not_null<HistoryItem*> item) const {
const auto nextBlockIndex = item->block()->indexInHistory() + 1;
const auto nextItemIndex = item->indexInBlock() + 1;
if (nextItemIndex < int(item->block()->items.size())) {
return item->block()->items[nextItemIndex];
if (nextItemIndex < int(item->block()->messages.size())) {
return item->block()->messages[nextItemIndex]->data();
} else if (nextBlockIndex < int(blocks.size())) {
return blocks[nextBlockIndex]->items.front();
return blocks[nextBlockIndex]->messages.front()->data();
}
return nullptr;
}
@ -2015,9 +1987,9 @@ HistoryItem *History::findPreviousItem(not_null<HistoryItem*> item) const {
const auto blockIndex = item->block()->indexInHistory();
const auto itemIndex = item->indexInBlock();
if (itemIndex > 0) {
return item->block()->items[itemIndex - 1];
return item->block()->messages[itemIndex - 1]->data();
} else if (blockIndex > 0) {
return blocks[blockIndex - 1]->items.back();
return blocks[blockIndex - 1]->messages.back()->data();
}
return nullptr;
}
@ -2162,14 +2134,14 @@ HistoryBlock *History::finishBuildingFrontBlock() {
auto block = _buildingFrontBlock->block;
if (block) {
if (blocks.size() > 1) {
auto last = block->items.back(); // ... item, item, item, last ], [ first, item, item ...
auto first = blocks[1]->items.front();
const auto last = block->messages.back()->data(); // ... item, item, item, last ], [ first, item, item ...
const auto first = blocks[1]->messages.front()->data();
// we've added a new front block, so previous item for
// the old first item of a first block was changed
first->previousItemChanged();
} else {
block->items.back()->nextItemChanged();
block->messages.back()->data()->nextItemChanged();
}
}
@ -2288,8 +2260,9 @@ void History::fixLastMessage(bool wasAtBottom) {
}
MsgId History::minMsgId() const {
for (auto block : std::as_const(blocks)) {
for (auto item : std::as_const(block->items)) {
for (const auto &block : blocks) {
for (const auto &message : block->messages) {
const auto item = message->data();
if (IsServerMsgId(item->id)) {
return item->id;
}
@ -2299,8 +2272,9 @@ MsgId History::minMsgId() const {
}
MsgId History::maxMsgId() const {
for (auto block : base::reversed(std::as_const(blocks))) {
for (auto item : base::reversed(std::as_const(block->items))) {
for (const auto &block : base::reversed(blocks)) {
for (const auto &message : base::reversed(block->messages)) {
const auto item = message->data();
if (IsServerMsgId(item->id)) {
return item->id;
}
@ -2325,7 +2299,7 @@ int History::resizeGetHeight(int newWidth) {
width = newWidth;
int y = 0;
for_const (auto block, blocks) {
for (const auto &block : blocks) {
block->setY(y);
y += block->resizeGetHeight(newWidth, resizeAllItems);
}
@ -2357,7 +2331,9 @@ History *History::migrateFrom() 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 {
@ -2365,10 +2341,10 @@ bool History::hasOrphanMediaGroupPart() const {
return false;
} else if (blocks.size() != 1) {
return false;
} else if (blocks.front()->items.size() != 1) {
} else if (blocks.front()->messages.size() != 1) {
return false;
}
return blocks.front()->items.front()->groupId() != MessageGroupId();
return blocks.front()->messages.front()->data()->groupId() != MessageGroupId();
}
bool History::removeOrphanMediaGroupPart() {
@ -2444,8 +2420,8 @@ void History::clearUpTill(MsgId availableMinId) {
return;
}
do {
auto item = blocks.front()->items.front();
auto itemId = item->id;
const auto item = blocks.front()->messages.front()->data();
const auto itemId = item->id;
if (IsServerMsgId(itemId) && itemId >= availableMinId) {
if (itemId == availableMinId) {
auto fromId = 0;
@ -2472,21 +2448,19 @@ void History::clearUpTill(MsgId availableMinId) {
void History::applyGroupAdminChanges(
const base::flat_map<UserId, bool> &changes) {
for (auto block : blocks) {
for (auto item : block->items) {
item->applyGroupAdminChanges(changes);
for (const auto &block : blocks) {
for (const auto &message : block->messages) {
message->data()->applyGroupAdminChanges(changes);
}
}
}
void History::clearBlocks(bool leaveItems) {
Blocks lst;
std::swap(lst, blocks);
for_const (HistoryBlock *block, lst) {
const auto cleared = base::take(blocks);
for (const auto &block : cleared) {
if (leaveItems) {
block->clear(true);
}
delete block;
}
}
@ -2518,7 +2492,7 @@ void History::changeMsgId(MsgId oldId, MsgId newId) {
}
void History::removeBlock(not_null<HistoryBlock*> block) {
Expects(block->items.empty());
Expects(block->messages.empty());
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
_buildingFrontBlock->block = nullptr;
@ -2530,9 +2504,9 @@ void History::removeBlock(not_null<HistoryBlock*> block) {
for (int i = index, l = blocks.size(); i < l; ++i) {
blocks[i]->setIndexInHistory(i);
}
blocks[index]->items.front()->previousItemChanged();
} else if (!blocks.empty() && !blocks.back()->items.empty()) {
blocks.back()->items.back()->nextItemChanged();
blocks[index]->messages.front()->data()->previousItemChanged();
} else if (!blocks.empty() && !blocks.back()->messages.empty()) {
blocks.back()->messages.back()->data()->nextItemChanged();
}
}
@ -2540,10 +2514,16 @@ History::~History() {
clearOnDestroy();
}
HistoryBlock::HistoryBlock(not_null<History*> history)
: _history(history) {
}
int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) {
auto y = 0;
for_const (auto item, items) {
item->setY(y);
for (const auto &message : messages) {
message->setY(y);
const auto item = message->data();
if (resizeAllItems || item->pendingResize()) {
y += item->resizeGetHeight(newWidth);
} else {
@ -2555,17 +2535,14 @@ int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) {
}
void HistoryBlock::clear(bool leaveItems) {
auto itemsList = base::take(items);
const auto list = base::take(messages);
if (leaveItems) {
for_const (auto item, itemsList) {
item->detachFast();
}
} else {
for_const (auto item, itemsList) {
delete item;
for (const auto &message : list) {
message->data()->detachFast();
}
}
// #TODO feeds delete all items in history
}
void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
@ -2594,30 +2571,31 @@ void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
if (_history->unreadBar == item) {
_history->unreadBar = nullptr;
}
if (_history->scrollTopItem == item) {
if (_history->scrollTopItem && _history->scrollTopItem->data() == item) {
_history->getNextScrollTopItem(this, itemIndex);
}
item->detachFast();
items.erase(items.begin() + itemIndex);
for (auto i = itemIndex, l = int(items.size()); i < l; ++i) {
items[i]->setIndexInBlock(i);
messages.erase(messages.begin() + itemIndex);
for (auto i = itemIndex, l = int(messages.size()); i < l; ++i) {
messages[i]->data()->setIndexInBlock(i);
}
if (items.empty()) {
if (messages.empty()) {
// Deletes this.
_history->removeBlock(this);
} else if (itemIndex < items.size()) {
items[itemIndex]->previousItemChanged();
} else if (itemIndex < messages.size()) {
messages[itemIndex]->data()->previousItemChanged();
} else if (blockIndex + 1 < _history->blocks.size()) {
_history->blocks[blockIndex + 1]->items.front()->previousItemChanged();
} else if (!_history->blocks.empty() && !_history->blocks.back()->items.empty()) {
_history->blocks.back()->items.back()->nextItemChanged();
}
if (items.empty()) {
delete this;
_history->blocks[blockIndex + 1]->messages.front()->data()->previousItemChanged();
} else if (!_history->blocks.empty() && !_history->blocks.back()->messages.empty()) {
_history->blocks.back()->messages.back()->data()->nextItemChanged();
}
if (needGroupRecount) {
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/flags.h"
class History;
class HistoryItem;
using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
@ -26,7 +27,10 @@ enum NewMessageType {
NewMessageExisting,
};
class History;
namespace HistoryView {
class Message;
} // namespace HistoryView
class Histories {
public:
using Map = QHash<PeerId, History*>;
@ -320,8 +324,7 @@ public:
void eraseFromUnreadMentions(MsgId msgId);
void addUnreadMentionsSlice(const MTPmessages_Messages &result);
using Blocks = std::deque<HistoryBlock*>;
Blocks blocks;
std::deque<std::unique_ptr<HistoryBlock>> blocks;
int width = 0;
int height = 0;
@ -394,7 +397,7 @@ public:
// 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
// resulting scrollTop = top(scrollTopItem) + scrollTopOffset
HistoryItem *scrollTopItem = nullptr;
HistoryView::Message *scrollTopItem = nullptr;
int scrollTopOffset = 0;
void forgetScrollState() {
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*> 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
// depending on if we are in isBuildingFronBlock() state.
@ -573,18 +579,14 @@ private:
class HistoryBlock {
public:
HistoryBlock(not_null<History*> history) : _history(history) {
}
HistoryBlock(not_null<History*> history);
HistoryBlock(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);
~HistoryBlock() {
clear();
}
void removeItem(not_null<HistoryItem*> item);
int resizeGetHeight(int newWidth, bool resizeAllItems);
@ -604,12 +606,16 @@ public:
HistoryBlock *previousBlock() const {
Expects(_indexInHistory >= 0);
return (_indexInHistory > 0) ? _history->blocks.at(_indexInHistory - 1) : nullptr;
return (_indexInHistory > 0)
? _history->blocks[_indexInHistory - 1].get()
: nullptr;
}
HistoryBlock *nextBlock() const {
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) {
_indexInHistory = index;
@ -617,7 +623,7 @@ public:
int indexInHistory() const {
Expects(_indexInHistory >= 0);
Expects(_indexInHistory < _history->blocks.size());
Expects(_history->blocks[_indexInHistory] == this);
Expects(_history->blocks[_indexInHistory].get() == this);
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_item_components.h"
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_message.h"
#include "ui/text_options.h"
#include "ui/widgets/popup_menu.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);
// 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 blockbottom = blocktop + block->height();
auto itemIndex = BinarySearchBlocksOrItems<TopToBottom>(block->items, searchEdge - blocktop);
auto itemIndex = BinarySearchBlocksOrItems<TopToBottom>(block->messages, searchEdge - blocktop);
while (true) {
while (true) {
auto item = block->items.at(itemIndex);
auto itemtop = blocktop + item->y();
auto itembottom = itemtop + item->height();
auto view = block->messages[itemIndex].get();
auto itemtop = blocktop + view->y();
auto itembottom = itemtop + view->data()->height();
// Binary search should've skipped all the items that are above / below the visible area.
if (TopToBottom) {
@ -228,7 +229,7 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
Assert(itemtop < _visibleAreaBottom);
}
if (!method(item, itemtop, itembottom)) {
if (!method(view, itemtop, itembottom)) {
return;
}
@ -244,7 +245,7 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
}
if (TopToBottom) {
if (++itemIndex >= block->items.size()) {
if (++itemIndex >= block->messages.size()) {
break;
}
} else {
@ -274,13 +275,13 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
return;
}
}
block = history->blocks[blockIndex];
block = history->blocks[blockIndex].get();
blocktop = historytop + block->y();
blockbottom = blocktop + block->height();
if (TopToBottom) {
itemIndex = 0;
} 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.
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.
auto message = item->toHistoryMessage();
const auto item = view->data();
const auto message = item->toHistoryMessage();
if (!message) return true;
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.
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()) {
lowestInOneDayItemBottom = itembottom - item->marginBottom();
}
@ -356,8 +359,8 @@ void HistoryInner::enumerateDates(Method method) {
if (itemtop > _visibleAreaTop) {
// Previous item (from the _migrated history) is drawing date now.
return false;
} else if (item == _history->blocks.front()->items.front() && item->isGroupMigrate()
&& _migrated->blocks.back()->items.back()->isGroupMigrate()) {
} else if (item == _history->blocks.front()->messages.front()->data() && item->isGroupMigrate()
&& _migrated->blocks.back()->messages.back()->data()->isGroupMigrate()) {
// This item is completely invisible and should be completely ignored.
return false;
}
@ -430,12 +433,11 @@ TextSelection HistoryInner::computeRenderSelection(
}
TextSelection HistoryInner::itemRenderSelection(
not_null<HistoryItem*> item,
not_null<Message*> view,
int selfromy,
int seltoy) const {
Expects(!item->detached());
const auto y = item->block()->y() + item->y();
const auto item = view->data();
const auto y = item->block()->y() + view->y();
if (y >= selfromy && y < seltoy) {
if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
return FullSelection;
@ -497,16 +499,17 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto hdrawtop = historyDrawTop();
if (mtop >= 0) {
auto iBlock = (_curHistory == _migrated ? _curBlock : (_migrated->blocks.size() - 1));
auto block = _migrated->blocks[iBlock];
auto iItem = (_curHistory == _migrated ? _curItem : (block->items.size() - 1));
auto item = block->items[iItem];
auto block = _migrated->blocks[iBlock].get();
auto iItem = (_curHistory == _migrated ? _curItem : (block->messages.size() - 1));
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.translate(0, y);
if (clip.y() < y + item->height()) while (y < drawToY) {
const auto selection = itemRenderSelection(
item,
view,
selfromy - mtop,
seltoy - mtop);
item->draw(p, clip.translated(0, -y), selection, ms);
@ -524,33 +527,35 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
y += h;
++iItem;
if (iItem == block->items.size()) {
if (iItem == block->messages.size()) {
iItem = 0;
++iBlock;
if (iBlock == _migrated->blocks.size()) {
break;
}
block = _migrated->blocks[iBlock];
block = _migrated->blocks[iBlock].get();
}
item = block->items[iItem];
view = block->messages[iItem].get();
item = view->data();
}
p.restore();
}
if (htop >= 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 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 y = htop + block->y() + item->y();
auto y = htop + block->y() + view->y();
p.save();
p.translate(0, y);
while (y < drawToY) {
auto h = item->height();
if (hclip.y() < y + h && hdrawtop < y + h) {
const auto selection = itemRenderSelection(
item,
view,
selfromy - htop,
seltoy - htop);
item->draw(p, hclip.translated(0, -y), selection, ms);
@ -567,15 +572,16 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
y += h;
++iItem;
if (iItem == block->items.size()) {
if (iItem == block->messages.size()) {
iItem = 0;
++iBlock;
if (iBlock == _history->blocks.size()) {
break;
}
block = _history->blocks[iBlock];
block = _history->blocks[iBlock].get();
}
item = block->items[iItem];
view = block->messages[iItem].get();
item = view->data();
}
p.restore();
}
@ -601,7 +607,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
//QDate lastDate;
//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
@ -1775,11 +1781,11 @@ void HistoryInner::recountHistoryGeometry() {
_historySkipHeight = 0;
if (_migrated) {
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()->items.back()->isGroupMigrate() && _history->blocks.front()->items.front()->isGroupMigrate()) {
_historySkipHeight += _history->blocks.front()->items.front()->height();
if (_migrated->blocks.back()->messages.back()->data()->date.date() == _history->blocks.front()->messages.front()->data()->date.date()) {
if (_migrated->blocks.back()->messages.back()->data()->isGroupMigrate() && _history->blocks.front()->messages.front()->data()->isGroupMigrate()) {
_historySkipHeight += _history->blocks.front()->messages.front()->data()->height();
} 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 newScrollDateItemTop = _history->scrollTopItem ? _history->scrollTopOffset : (_migrated ? _migrated->scrollTopOffset : 0);
//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;
// }
//}
@ -2065,32 +2071,34 @@ void HistoryInner::adjustCurrent(int32 y, History *history) const {
++_curBlock;
_curItem = 0;
}
auto block = history->blocks[_curBlock];
if (_curItem >= block->items.size()) {
_curItem = block->items.size() - 1;
auto block = history->blocks[_curBlock].get();
if (_curItem >= block->messages.size()) {
_curItem = block->messages.size() - 1;
}
auto by = block->y();
while (block->items[_curItem]->y() + by > y && _curItem > 0) {
while (block->messages[_curItem]->y() + by > y && _curItem > 0) {
--_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;
}
}
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();
if (itemIndex > 0) {
return block->items.at(itemIndex - 1);
return block->messages[itemIndex - 1]->data();
}
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()) {
return _migrated->blocks.back()->items.back();
return _migrated->blocks.back()->messages.back()->data();
}
return nullptr;
}
@ -2098,16 +2106,16 @@ HistoryItem *HistoryInner::prevItem(HistoryItem *item) {
HistoryItem *HistoryInner::nextItem(HistoryItem *item) {
if (!item || item->detached()) return nullptr;
HistoryBlock *block = item->block();
const auto block = item->block();
int blockIndex = block->indexInHistory(), itemIndex = item->indexInBlock();
if (itemIndex + 1 < block->items.size()) {
return block->items.at(itemIndex + 1);
if (itemIndex + 1 < block->messages.size()) {
return block->messages[itemIndex + 1]->data();
}
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()) {
return _history->blocks.front()->items.front();
return _history->blocks.front()->messages.front()->data();
}
return nullptr;
}
@ -2202,8 +2210,8 @@ void HistoryInner::onUpdateSelected() {
adjustCurrent(point.y());
if (_curHistory && !_curHistory->isEmpty()) {
block = _curHistory->blocks[_curBlock];
item = block->items[_curItem];
block = _curHistory->blocks[_curBlock].get();
item = block->messages[_curItem]->data();
App::mousedItem(item);
m = mapPointToItem(point, item);
@ -2476,12 +2484,10 @@ int HistoryInner::historyScrollTop() const {
auto htop = historyTop();
auto mtop = migratedTop();
if (htop >= 0 && _history->scrollTopItem) {
Assert(!_history->scrollTopItem->detached());
return htop + _history->scrollTopItem->block()->y() + _history->scrollTopItem->y() + _history->scrollTopOffset;
return htop + _history->scrollTopItem->data()->block()->y() + _history->scrollTopItem->y() + _history->scrollTopOffset;
}
if (mtop >= 0 && _migrated->scrollTopItem) {
Assert(!_migrated->scrollTopItem->detached());
return mtop + _migrated->scrollTopItem->block()->y() + _migrated->scrollTopItem->y() + _migrated->scrollTopOffset;
return mtop + _migrated->scrollTopItem->data()->block()->y() + _migrated->scrollTopItem->y() + _migrated->scrollTopOffset;
}
return ScrollMax;
}
@ -2505,7 +2511,7 @@ int HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not b
if (item->detached()) return -1;
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() {
@ -2531,7 +2537,10 @@ void HistoryInner::notifyMigrateUpdated() {
_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()) {
int top = itemTop(item);
if (top >= oldKeyboardTop) {
@ -2706,9 +2715,9 @@ void HistoryInner::addSelectionRange(
int toitem) const {
if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) {
for (; fromblock <= toblock; ++fromblock) {
auto block = history->blocks[fromblock];
for (int cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto item = block->items[fromitem];
auto block = history->blocks[fromblock].get();
for (int cnt = (fromblock < toblock) ? block->messages.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto item = block->messages[fromitem]->data();
changeSelectionAsGroup(toItems, item, SelectAction::Select);
}
if (toItems->size() >= MaxSelectedItems) break;
@ -2743,7 +2752,7 @@ void HistoryInner::applyDragSelection(
toblock = -1;
toitem = -1;
} 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;
fromitem = 0;

View File

@ -122,7 +122,7 @@ private slots:
private:
class BotAbout;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
using Message = HistoryView::Message;
enum class MouseAction {
None,
PrepareDrag,
@ -161,7 +161,7 @@ private:
HistoryItem *nextItem(HistoryItem *item);
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting);
TextSelection itemRenderSelection(
not_null<HistoryItem*> item,
not_null<Message*> view,
int selfromy,
int seltoy) const;
TextSelection computeRenderSelection(
@ -291,7 +291,7 @@ private:
Animation _scrollDateOpacity;
SingleQueuedInvokation _scrollDateCheck;
SingleTimer _scrollDateHideTimer;
HistoryItem *_scrollDateLastItem = nullptr;
Message *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0;
ClickHandlerPtr _scrollDateLink;
@ -302,7 +302,7 @@ private:
// 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.
//
// 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.
template <bool TopToBottom, typename 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 "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_media_types.h"
#include "history/history_media_grouped.h"
#include "history/history_message.h"
#include "history/view/history_view_service_message.h"
#include "media/media_clip_reader.h"
#include "styles/style_dialogs.h"
#include "styles/style_history.h"
@ -347,9 +348,22 @@ void HistoryItem::detach() {
_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() {
_block = nullptr;
_indexInBlock = -1;
_mainView = nullptr;
validateGroupId();
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() {
Expects(!isLogEntry());
setDisplayDate([&] {

View File

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

View File

@ -13,18 +13,25 @@ struct HistoryServiceDependentData {
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;
};
struct HistoryServicePayment : public RuntimeComponent<HistoryServicePayment>, public HistoryServiceDependentData {
struct HistoryServicePayment
: public RuntimeComponent<HistoryServicePayment>
, public HistoryServiceDependentData {
QString amount;
};
struct HistoryServiceSelfDestruct : public RuntimeComponent<HistoryServiceSelfDestruct> {
struct HistoryServiceSelfDestruct
: public RuntimeComponent<HistoryServiceSelfDestruct> {
enum class Type {
Photo,
Video,

View File

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

View File

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

View File

@ -23,8 +23,12 @@ class Controller;
namespace HistoryView {
enum class Context : char;
class Message;
class ListDelegate {
public:
virtual Context listContext() = 0;
virtual void listScrollTo(int top) = 0;
virtual void listCloseRequest() = 0;
virtual rpl::producer<Data::MessagesSlice> listSource(
@ -138,15 +142,18 @@ private:
void saveScrollState();
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 mouseActionUpdate(const QPoint &screenPos);
void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button);
void mouseActionCancel();
void updateSelected();
void performDrag();
int itemTop(not_null<const HistoryItem*> item) const;
void repaintItem(const HistoryItem *item);
QPoint mapPointToItem(QPoint point, const HistoryItem *item) const;
int itemTop(not_null<const Message*> view) const;
void repaintItem(const Message *view);
QPoint mapPointToItem(QPoint point, const Message *view) const;
void handlePendingHistoryResize();
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
@ -165,8 +172,8 @@ private:
const TextWithEntities &forClipboard,
QClipboard::Mode mode = QClipboard::Clipboard);
not_null<HistoryItem*> findItemByY(int y) const;
HistoryItem *strictFindItemByY(int y) const;
not_null<Message*> findItemByY(int y) const;
Message *strictFindItemByY(int y) const;
int findNearestItem(Data::MessagePosition position) const;
void checkMoveToOtherViewer();
@ -184,7 +191,7 @@ private:
// 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.
//
// 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.
template <EnumItemsDirection direction, typename Method>
void enumerateItems(Method method);
@ -210,18 +217,19 @@ private:
not_null<ListDelegate*> _delegate;
not_null<Window::Controller*> _controller;
Data::MessagePosition _aroundPosition;
Context _context;
int _aroundIndex = -1;
int _idsLimit = kMinimalIdsLimit;
Data::MessagesSlice _slice;
std::vector<not_null<HistoryItem*>> _items;
std::map<uint64, HistoryItem*> _itemsByIds;
std::vector<not_null<Message*>> _items;
std::map<not_null<HistoryItem*>, std::unique_ptr<Message>, std::less<>> _views;
int _itemsTop = 0;
int _itemsHeight = 0;
int _minHeight = 0;
int _visibleTop = 0;
int _visibleBottom = 0;
HistoryItem *_visibleTopItem = nullptr;
Message *_visibleTopItem = nullptr;
int _visibleTopFromItem = 0;
ScrollTopState _scrollTopState;
@ -229,19 +237,19 @@ private:
Animation _scrollDateOpacity;
SingleQueuedInvokation _scrollDateCheck;
base::Timer _scrollDateHideTimer;
HistoryItem *_scrollDateLastItem = nullptr;
Message *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0;
MouseAction _mouseAction = MouseAction::None;
TextSelectType _mouseSelectType = TextSelectType::Letters;
QPoint _dragStartPosition;
QPoint _mousePosition;
HistoryItem *_mouseActionItem = nullptr;
Message *_mouseActionItem = nullptr;
HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false;
HistoryItem *_selectedItem = nullptr;
Message *_selectedItem = nullptr;
TextSelection _selectedText;
bool _wasSelectedText = false; // was some text selected in current drag action
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_media.h"
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_message.h"
#include "lang/lang_keys.h"
#include "lang/lang_cloud_manager.h"
#include "boxes/add_contact_box.h"
@ -1134,8 +1135,9 @@ void MainWidget::deleteAllFromUser(ChannelData *channel, UserData *from) {
QVector<MsgId> toDestroy;
if (auto history = App::historyLoaded(channel->id)) {
for_const (auto block, history->blocks) {
for_const (auto item, block->items) {
for (const auto &block : history->blocks) {
for (const auto &message : block->messages) {
const auto item = message->data();
if (item->from() == from && item->canDelete()) {
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 "info/info_memento.h"
#include "history/view/history_view_message.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "styles/style_window.h"
@ -252,13 +253,13 @@ void Controller::showJumpToDate(not_null<PeerData*> peer, QDate requestedDate) {
auto currentPeerDate = [peer] {
if (auto history = App::historyLoaded(peer)) {
if (history->scrollTopItem) {
return history->scrollTopItem->date.date();
return history->scrollTopItem->data()->date.date();
} else if (history->loadedAtTop() && !history->isEmpty() && history->peer->migrateFrom()) {
if (auto migrated = App::historyLoaded(history->peer->migrateFrom())) {
if (migrated->scrollTopItem) {
// We're up in the migrated history.
// 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()) {
@ -287,7 +288,7 @@ void Controller::showJumpToDate(not_null<PeerData*> peer, QDate requestedDate) {
if (auto history = App::historyLoaded(chat)) {
if (history->loadedAtTop()) {
if (!history->isEmpty()) {
return history->blocks.front()->items.front()->date.date();
return history->blocks.front()->messages.front()->data()->date.date();
}
} else {
return startDate();
@ -297,7 +298,7 @@ void Controller::showJumpToDate(not_null<PeerData*> peer, QDate requestedDate) {
if (auto history = App::historyLoaded(peer)) {
if (history->loadedAtTop()) {
if (!history->isEmpty()) {
return history->blocks.front()->items.front()->date.date();
return history->blocks.front()->messages.front()->data()->date.date();
}
return QDate::currentDate();
}

View File

@ -227,6 +227,8 @@
<(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.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.h
<(src_loc)/history/view/history_view_top_bar_widget.cpp