Support userpics and dates in channel log events.

This commit is contained in:
John Preston 2017-06-22 04:31:02 +03:00
parent 5c87b42135
commit 0a39e7e2b1
6 changed files with 379 additions and 189 deletions

View File

@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_history.h"
#include "history/history_media_types.h"
#include "history/history_service_layout.h"
#include "history/history_admin_log_section.h"
#include "mainwindow.h"
#include "window/window_controller.h"
@ -32,10 +33,166 @@ namespace AdminLog {
namespace {
constexpr auto kScrollDateHideTimeout = 1000;
constexpr auto kEventsPerPage = 10;
constexpr auto kEventsPerPage = 1;
} // namespace
template <InnerWidget::EnumItemsDirection direction, typename Method>
void InnerWidget::enumerateItems(Method method) {
constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom);
// No displayed messages in this history.
if (_items.empty()) {
return;
}
if (_visibleBottom <= _itemsTop || _itemsTop + _itemsHeight <= _visibleTop) {
return;
}
auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = TopToBottom ? std::lower_bound(begin, end, _visibleTop, [this](auto &elem, int top) {
return itemTop(elem) + elem->height() <= top;
}) : std::upper_bound(begin, end, _visibleBottom, [this](int bottom, auto &elem) {
return itemTop(elem) + elem->height() >= bottom;
});
auto wasEnd = (from == end);
if (wasEnd) {
--from;
}
if (TopToBottom) {
t_assert(itemTop(from->get()) + from->get()->height() > _visibleTop);
} else {
t_assert(itemTop(from->get()) < _visibleBottom);
}
while (true) {
auto item = from->get();
auto itemtop = itemTop(item);
auto itembottom = itemtop + item->height();
// Binary search should've skipped all the items that are above / below the visible area.
if (TopToBottom) {
t_assert(itembottom > _visibleTop);
} else {
t_assert(itemtop < _visibleBottom);
}
if (!method(item, itemtop, itembottom)) {
return;
}
// Skip all the items that are below / above the visible area.
if (TopToBottom) {
if (itembottom >= _visibleBottom) {
return;
}
} else {
if (itemtop <= _visibleTop) {
return;
}
}
if (TopToBottom) {
if (++from == end) {
break;
}
} else {
if (from == begin) {
break;
}
--from;
}
}
}
template <typename Method>
void InnerWidget::enumerateUserpics(Method method) {
// Find and remember the top of an attached messages pack
// -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) {
// Skip all service messages.
auto message = item->toHistoryMessage();
if (!message) return true;
if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) {
lowestAttachedItemTop = itemtop + message->marginTop();
}
// Call method on a userpic for all messages that have it and for those who are not showing it
// because of their attachment to the next message if they are bottom-most visible.
if (message->displayFromPhoto() || (message->hasFromPhoto() && itembottom >= _visibleBottom)) {
if (lowestAttachedItemTop < 0) {
lowestAttachedItemTop = itemtop + message->marginTop();
}
// Attach userpic to the bottom of the visible area with the same margin as the last message.
auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom();
auto userpicBottom = qMin(itembottom - message->marginBottom(), _visibleBottom - userpicMinBottomSkip);
// Do not let the userpic go above the attached messages pack top line.
userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize);
// Call the template callback function that was passed
// and return if it finished everything it needed.
if (!method(message, userpicBottom - st::msgPhotoSize)) {
return false;
}
}
// Forget the found top of the pack, search for the next one from scratch.
if (!message->isAttachedToNext()) {
lowestAttachedItemTop = -1;
}
return true;
};
enumerateItems<EnumItemsDirection::TopToBottom>(userpicCallback);
}
template <typename Method>
void InnerWidget::enumerateDates(Method method) {
// Find and remember the bottom of an single-day messages pack
// -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) {
if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) {
lowestInOneDayItemBottom = itembottom - item->marginBottom();
}
// Call method on a date for all messages that have it and for those who are not showing it
// because they are in a one day together with the previous message if they are top-most visible.
if (item->displayDate() || (!item->isEmpty() && itemtop <= _visibleTop)) {
if (lowestInOneDayItemBottom < 0) {
lowestInOneDayItemBottom = itembottom - item->marginBottom();
}
// Attach date to the top of the visible area with the same margin as it has in service message.
auto dateTop = qMax(itemtop, _visibleTop) + st::msgServiceMargin.top();
// Do not let the date go below the single-day messages pack bottom line.
auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
dateTop = qMin(dateTop, lowestInOneDayItemBottom - dateHeight);
// Call the template callback function that was passed
// and return if it finished everything it needed.
if (!method(item, itemtop, dateTop)) {
return false;
}
}
// Forget the found bottom of the pack, search for the next one from scratch.
if (!item->isInOneDayWithPrevious()) {
lowestInOneDayItemBottom = -1;
}
return true;
};
enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback);
}
InnerWidget::InnerWidget(QWidget *parent, gsl::not_null<Window::Controller*> controller, gsl::not_null<ChannelData*> channel, base::lambda<void(int top)> scrollTo) : TWidget(parent)
, _controller(controller)
, _channel(channel)
@ -62,17 +219,23 @@ InnerWidget::InnerWidget(QWidget *parent, gsl::not_null<Window::Controller*> con
}
void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) {
auto scrolledUp = (visibleTop < _visibleTop);
_visibleTop = visibleTop;
_visibleBottom = visibleBottom;
updateVisibleTopItem();
checkPreloadMore();
if (scrolledUp) {
_scrollDateCheck.call();
} else {
scrollDateHideByTimer();
}
}
void InnerWidget::updateVisibleTopItem() {
auto start = std::rbegin(_items), end = std::rend(_items);
auto from = std::upper_bound(start, end, _visibleTop, [](int top, auto &elem) {
return top <= elem->y() + elem->height();
auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = std::lower_bound(begin, end, _visibleTop, [this](auto &elem, int top) {
return itemTop(elem) + elem->height() <= top;
});
if (from != end) {
_visibleTopItem = *from;
@ -105,9 +268,7 @@ void InnerWidget::scrollDateCheck() {
void InnerWidget::scrollDateHideByTimer() {
_scrollDateHideTimer.cancel();
if (!_scrollDateLink || ClickHandler::getPressed() != _scrollDateLink) {
scrollDateHide();
}
scrollDateHide();
}
void InnerWidget::scrollDateHide() {
@ -116,13 +277,6 @@ void InnerWidget::scrollDateHide() {
}
}
void InnerWidget::keepScrollDateForNow() {
if (!_scrollDateShown && _scrollDateLastItem && _scrollDateOpacity.animating()) {
toggleScrollDateShown();
}
_scrollDateHideTimer.callOnce(kScrollDateHideTimeout);
}
void InnerWidget::toggleScrollDateShown() {
_scrollDateShown = !_scrollDateShown;
auto from = _scrollDateShown ? 0. : 1.;
@ -233,7 +387,8 @@ void InnerWidget::preloadMore(Direction direction) {
App::feedChats(results.vchats);
auto &events = results.vevents.v;
if (!events.empty()) {
_items.reserve(_items.size() + events.size());
auto oldItemsCount = _items.size();
_items.reserve(oldItemsCount + events.size() * 2);
for_const (auto &event, events) {
t_assert(event.type() == mtpc_channelAdminLogEvent);
auto &data = event.c_channelAdminLogEvent();
@ -253,14 +408,29 @@ void InnerWidget::preloadMore(Direction direction) {
}
}
}
if (!_items.empty()) {
auto newItemsCount = _items.size();
if (newItemsCount != oldItemsCount) {
for (auto i = oldItemsCount; i != newItemsCount + 1; ++i) {
if (i > 0) {
auto item = _items[i - 1].get();
if (i == newItemsCount) {
item->setLogEntryDisplayDate(true);
} else {
auto previous = _items[i].get();
item->setLogEntryDisplayDate(item->date.date() != previous->date.date());
auto attachToPrevious = item->computeIsAttachToPrevious(previous);
item->setLogEntryAttachToPrevious(attachToPrevious);
previous->setLogEntryAttachToNext(attachToPrevious);
}
}
}
_maxId = (--_itemsByIds.end())->first;
_minId = _itemsByIds.begin()->first;
if (_minId == 1) {
_upLoaded = true;
}
itemsAdded(direction);
}
itemsAdded(direction);
} else {
loadedFlag = true;
}
@ -308,12 +478,12 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
if (_items.empty() && _upLoaded && _downLoaded) {
paintEmpty(p);
} else {
auto start = std::rbegin(_items), end = std::rend(_items);
auto from = std::upper_bound(start, end, clip.top(), [this](int top, auto &elem) {
return top <= itemTop(elem.get()) + elem->height();
auto begin = std::rbegin(_items), end = std::rend(_items);
auto from = std::lower_bound(begin, end, clip.top(), [this](auto &elem, int top) {
return itemTop(elem) + elem->height() <= top;
});
auto to = std::lower_bound(start, end, clip.top() + clip.height(), [this](auto &elem, int bottom) {
return itemTop(elem.get()) < bottom;
auto to = std::lower_bound(begin, end, clip.top() + clip.height(), [this](auto &elem, int bottom) {
return itemTop(elem) < bottom;
});
if (from != end) {
auto top = itemTop(from->get());
@ -324,6 +494,58 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
top += height;
p.translate(0, height);
}
p.translate(0, -top);
enumerateUserpics([&p, &clip](HistoryMessage *message, int userpicTop) {
// stop the enumeration if the userpic is below the painted rect
if (userpicTop >= clip.top() + clip.height()) {
return false;
}
// paint the userpic if it intersects the painted rect
if (userpicTop + st::msgPhotoSize > clip.top()) {
message->from()->paintUserpicLeft(p, st::historyPhotoLeft, userpicTop, message->width(), st::msgPhotoSize);
}
return true;
});
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*/](HistoryItem *item, int itemtop, int dateTop) {
// stop the enumeration if the date is above the painted rect
if (dateTop + dateHeight <= clip.top()) {
return false;
}
bool displayDate = item->displayDate();
bool dateInPlace = displayDate;
if (dateInPlace) {
int correctDateTop = itemtop + st::msgServiceMargin.top();
dateInPlace = (dateTop < correctDateTop + dateHeight);
}
//bool noFloatingDate = (item->date.date() == lastDate && displayDate);
//if (noFloatingDate) {
// if (itemtop < showFloatingBefore) {
// noFloatingDate = false;
// }
//}
// paint the date if it intersects the painted rect
if (dateTop < clip.top() + clip.height()) {
auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
if (opacity > 0.) {
p.setOpacity(opacity);
int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top());
int width = item->width();
if (auto date = item->Get<HistoryMessageDate>()) {
date->paint(p, dateY, width);
} else {
HistoryLayout::ServiceMessagePainter::paintDate(p, item->date, dateY, width);
}
}
}
return true;
});
}
}
}
@ -354,18 +576,10 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
}
void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
static auto lastGlobalPosition = e->globalPos();
auto reallyMoved = (lastGlobalPosition != e->globalPos());
auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton));
if (!buttonsPressed && _mouseAction != MouseAction::None) {
mouseReleaseEvent(e);
}
if (reallyMoved) {
lastGlobalPosition = e->globalPos();
if (!buttonsPressed || (_scrollDateLink && ClickHandler::getPressed() == _scrollDateLink)) {
keepScrollDateForNow();
}
}
mouseActionUpdate(e->globalPos());
}
@ -531,9 +745,9 @@ void InnerWidget::updateSelected() {
auto point = QPoint(snap(mousePosition.x(), 0, width()), snap(mousePosition.y(), _visibleTop, _visibleBottom));
auto itemPoint = QPoint();
auto start = std::rbegin(_items), end = std::rend(_items);
auto from = (point.y() >= _itemsTop && point.y() < _itemsTop + _itemsHeight) ? std::upper_bound(start, end, point.y(), [this](int top, auto &elem) {
return top <= itemTop(elem.get()) + elem->height();
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 itemTop(elem) + elem->height() <= top;
}) : end;
auto item = (from != end) ? from->get() : nullptr;
if (item) {
@ -561,83 +775,31 @@ void InnerWidget::updateSelected() {
InvokeQueued(this, [this] { performDrag(); });
}
}
HistoryStateRequest request;
if (_mouseAction == MouseAction::Selecting) {
request.flags |= Text::StateRequest::Flag::LookupSymbol;
} else {
selectingText = false;
}
dragState = item->getState(itemPoint, request);
lnkhost = item;
if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) {
if (auto msg = item->toHistoryMessage()) {
if (msg->hasFromPhoto()) {
enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {
// stop enumeration if the userpic is below our point
if (userpicTop > point.y()) {
return false;
}
auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
auto scrollDateOpacity = _scrollDateOpacity.current(_scrollDateShown ? 1. : 0.);
//enumerateDates([this, &dragState, &lnkhost, &point, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](HistoryItem *item, int itemtop, int dateTop) {
// // stop enumeration if the date is above our point
// if (dateTop + dateHeight <= point.y()) {
// return false;
// }
// bool displayDate = item->displayDate();
// bool dateInPlace = displayDate;
// if (dateInPlace) {
// int correctDateTop = itemtop + st::msgServiceMargin.top();
// dateInPlace = (dateTop < correctDateTop + dateHeight);
// }
// // stop enumeration if we've found a date under the cursor
// if (dateTop <= point.y()) {
// auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity;
// if (opacity > 0.) {
// auto dateWidth = 0;
// if (auto date = item->Get<HistoryMessageDate>()) {
// dateWidth = date->_width;
// } else {
// dateWidth = st::msgServiceFont->width(langDayOfMonthFull(item->date.date()));
// }
// dateWidth += st::msgServicePadding.left() + st::msgServicePadding.right();
// auto dateLeft = st::msgServiceMargin.left();
// auto maxwidth = item->history()->width;
// if (Adaptive::ChatWide()) {
// maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
// }
// auto widthForDate = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
// dateLeft += (widthForDate - dateWidth) / 2;
// if (point.x() >= dateLeft && point.x() < dateLeft + dateWidth) {
// if (!_scrollDateLink) {
// _scrollDateLink = MakeShared<DateClickHandler>(item->history()->peer, item->date.date());
// } else {
// static_cast<DateClickHandler*>(_scrollDateLink.data())->setDate(item->date.date());
// }
// dragState.link = _scrollDateLink;
// lnkhost = item;
// }
// }
// return false;
// }
// return true;
//}); // TODO
if (!dragState.link) {
HistoryStateRequest request;
if (_mouseAction == MouseAction::Selecting) {
request.flags |= Text::StateRequest::Flag::LookupSymbol;
} else {
selectingText = false;
}
dragState = item->getState(itemPoint, request);
lnkhost = item;
if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) {
if (auto msg = item->toHistoryMessage()) {
if (msg->hasFromPhoto()) {
//enumerateUserpics([&dragState, &lnkhost, &point](HistoryMessage *message, int userpicTop) -> bool {
// // stop enumeration if the userpic is below our point
// if (userpicTop > point.y()) {
// return false;
// }
// // stop enumeration if we've found a userpic under the cursor
// if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
// dragState.link = message->from()->openLink();
// lnkhost = message;
// return false;
// }
// return true;
//}); // TODO
}
// stop enumeration if we've found a userpic under the cursor
if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
dragState.link = message->from()->openLink();
lnkhost = message;
return false;
}
return true;
});
}
}
}

View File

@ -111,6 +111,10 @@ private:
Dragging,
Selecting,
};
enum class EnumItemsDirection {
TopToBottom,
BottomToTop,
};
void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
void mouseActionUpdate(const QPoint &screenPos);
@ -134,10 +138,33 @@ private:
void repaintScrollDateCallback();
bool displayScrollDate() const;
void scrollDateHide();
void keepScrollDateForNow();
void scrollDateCheck();
void scrollDateHideByTimer();
// 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
// if it returns false the enumeration stops immidiately.
template <EnumItemsDirection direction, typename Method>
void enumerateItems(Method method);
// This function finds all userpics on the left that are displayed and calls template method
// for each found userpic (from the top to the bottom) using enumerateItems() method.
//
// Method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature
// if it returns false the enumeration stops immidiately.
template <typename Method>
void enumerateUserpics(Method method);
// This function finds all date elements that are displayed and calls template method
// for each found date element (from the bottom to the top) using enumerateItems() method.
//
// Method has "bool (*Method)(HistoryItem *item, int itemtop, int dateTop)" signature
// if it returns false the enumeration stops immidiately.
template <typename Method>
void enumerateDates(Method method);
gsl::not_null<Window::Controller*> _controller;
gsl::not_null<ChannelData*> _channel;
gsl::not_null<History*> _history;
@ -161,7 +188,6 @@ private:
base::Timer _scrollDateHideTimer;
HistoryItem *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0;
ClickHandlerPtr _scrollDateLink;
// Up - max, Down - min.
uint64 _maxId = 0;

View File

@ -57,7 +57,27 @@ private:
};
std::unique_ptr<QMimeData> mimeDataFromTextWithEntities(const TextWithEntities &forClipboard) {
// Helper binary search for an item in a list that is not completely
// above the given top of the visible area or below the given bottom of the visible area
// is applied once for blocks list in a history and once for items list in the found block.
template <bool TopToBottom, typename T>
int BinarySearchBlocksOrItems(const T &list, int edge) {
// static_cast to work around GCC bug #78693
auto start = 0, end = static_cast<int>(list.size());
while (end - start > 1) {
auto middle = (start + end) / 2;
auto top = list[middle]->y();
auto chooseLeft = (TopToBottom ? (top <= edge) : (top < edge));
if (chooseLeft) {
start = middle;
} else {
end = middle;
}
}
return start;
}
std::unique_ptr<QMimeData> MimeDataFromTextWithEntities(const TextWithEntities &forClipboard) {
if (forClipboard.text.isEmpty()) {
return nullptr;
}
@ -149,33 +169,9 @@ void HistoryInner::repaintItem(const HistoryItem *item) {
}
}
namespace {
// helper binary search for an item in a list that is not completely
// above the given top of the visible area or below the given bottom of the visible area
// is applied once for blocks list in a history and once for items list in the found block
template <bool TopToBottom, typename T>
int binarySearchBlocksOrItems(const T &list, int edge) {
// static_cast to work around GCC bug #78693
auto start = 0, end = static_cast<int>(list.size());
while (end - start > 1) {
auto middle = (start + end) / 2;
auto top = list[middle]->y();
auto chooseLeft = (TopToBottom ? (top <= edge) : (top < edge));
if (chooseLeft) {
start = middle;
} else {
end = middle;
}
}
return start;
}
} // namespace
template <bool TopToBottom, typename Method>
void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Method method) {
// no displayed messages in this history
// No displayed messages in this history.
if (historytop < 0 || history->isEmpty()) {
return;
}
@ -185,14 +181,14 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
auto searchEdge = TopToBottom ? _visibleAreaTop : _visibleAreaBottom;
// binary search for blockIndex of the first block that is not completely below the visible area
auto blockIndex = binarySearchBlocksOrItems<TopToBottom>(history->blocks, searchEdge - historytop);
// Binary search for blockIndex of the first block that is not completely below the visible area.
auto blockIndex = BinarySearchBlocksOrItems<TopToBottom>(history->blocks, searchEdge - historytop);
// binary search for itemIndex of the first item that is not completely below the visible area
// Binary search for itemIndex of the first item that is not completely below the visible area.
auto block = history->blocks.at(blockIndex);
auto blocktop = historytop + block->y();
auto blockbottom = blocktop + block->height();
auto itemIndex = binarySearchBlocksOrItems<TopToBottom>(block->items, searchEdge - blocktop);
auto itemIndex = BinarySearchBlocksOrItems<TopToBottom>(block->items, searchEdge - blocktop);
while (true) {
while (true) {
@ -200,7 +196,7 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
auto itemtop = blocktop + item->y();
auto itembottom = itemtop + item->height();
// binary search should've skipped all the items that are above / below the visible area
// Binary search should've skipped all the items that are above / below the visible area.
if (TopToBottom) {
t_assert(itembottom > _visibleAreaTop);
} else {
@ -211,7 +207,7 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
return;
}
// skip all the items that are below / above the visible area
// Skip all the items that are below / above the visible area.
if (TopToBottom) {
if (itembottom >= _visibleAreaBottom) {
return;
@ -233,7 +229,7 @@ void HistoryInner::enumerateItemsInHistory(History *history, int historytop, Met
}
}
// skip all the rest blocks that are below / above the visible area
// Skip all the rest blocks that are below / above the visible area.
if (TopToBottom) {
if (blockbottom >= _visibleAreaBottom) {
return;
@ -270,12 +266,12 @@ void HistoryInner::enumerateUserpics(Method method) {
return;
}
// find and remember the top of an attached messages pack
// -1 means we didn't find an attached to next message yet
// Find and remember the top of an attached messages pack
// -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) {
// skip all service messages
// Skip all service messages.
auto message = item->toHistoryMessage();
if (!message) return true;
@ -283,27 +279,27 @@ void HistoryInner::enumerateUserpics(Method method) {
lowestAttachedItemTop = itemtop + message->marginTop();
}
// call method on a userpic for all messages that have it and for those who are not showing it
// because of their attachment to the next message if they are bottom-most visible
// Call method on a userpic for all messages that have it and for those who are not showing it
// because of their attachment to the next message if they are bottom-most visible.
if (message->displayFromPhoto() || (message->hasFromPhoto() && itembottom >= _visibleAreaBottom)) {
if (lowestAttachedItemTop < 0) {
lowestAttachedItemTop = itemtop + message->marginTop();
}
// attach userpic to the bottom of the visible area with the same margin as the last message
// Attach userpic to the bottom of the visible area with the same margin as the last message.
auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom();
auto userpicBottom = qMin(itembottom - message->marginBottom(), _visibleAreaBottom - userpicMinBottomSkip);
// do not let the userpic go above the attached messages pack top line
// Do not let the userpic go above the attached messages pack top line.
userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize);
// call the template callback function that was passed
// and return if it finished everything it needed
// Call the template callback function that was passed
// and return if it finished everything it needed.
if (!method(message, userpicBottom - st::msgPhotoSize)) {
return false;
}
}
// forget the found top of the pack, search for the next one from scratch
// Forget the found top of the pack, search for the next one from scratch.
if (!message->isAttachedToNext()) {
lowestAttachedItemTop = -1;
}
@ -316,28 +312,28 @@ void HistoryInner::enumerateUserpics(Method method) {
template <typename Method>
void HistoryInner::enumerateDates(Method method) {
int drawtop = historyDrawTop();
auto drawtop = historyDrawTop();
// find and remember the bottom of an single-day messages pack
// -1 means we didn't find a same-day with previous message yet
int lowestInOneDayItemBottom = -1;
// Find and remember the bottom of an single-day messages pack
// -1 means we didn't find a same-day with previous message yet.
auto lowestInOneDayItemBottom = -1;
auto dateCallback = [this, &lowestInOneDayItemBottom, &method, drawtop](HistoryItem *item, int itemtop, int itembottom) {
if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) {
lowestInOneDayItemBottom = itembottom - item->marginBottom();
}
// call method on a date for all messages that have it and for those who are not showing it
// because they are in a one day together with the previous message if they are top-most visible
// Call method on a date for all messages that have it and for those who are not showing it
// because they are in a one day together with the previous message if they are top-most visible.
if (item->displayDate() || (!item->isEmpty() && itemtop <= _visibleAreaTop)) {
// skip the date of history migrate item if it will be in migrated
if (itemtop < drawtop && item->history() == _history) {
if (itemtop > _visibleAreaTop) {
// previous item (from the _migrated history) is drawing date now
// Previous item (from the _migrated history) is drawing date now.
return false;
} else if (item == _history->blocks.front()->items.front() && item->isGroupMigrate()
&& _migrated->blocks.back()->items.back()->isGroupMigrate()) {
// this item is completely invisible and should be completely ignored
// This item is completely invisible and should be completely ignored.
return false;
}
}
@ -345,21 +341,21 @@ void HistoryInner::enumerateDates(Method method) {
if (lowestInOneDayItemBottom < 0) {
lowestInOneDayItemBottom = itembottom - item->marginBottom();
}
// attach date to the top of the visible area with the same margin as it has in service message
// Attach date to the top of the visible area with the same margin as it has in service message.
int dateTop = qMax(itemtop, _visibleAreaTop) + st::msgServiceMargin.top();
// do not let the date go below the single-day messages pack bottom line
// Do not let the date go below the single-day messages pack bottom line.
int dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
dateTop = qMin(dateTop, lowestInOneDayItemBottom - dateHeight);
// call the template callback function that was passed
// and return if it finished everything it needed
// Call the template callback function that was passed
// and return if it finished everything it needed.
if (!method(item, itemtop, dateTop)) {
return false;
}
}
// forget the found bottom of the pack, search for the next one from scratch
// Forget the found bottom of the pack, search for the next one from scratch.
if (!item->isInOneDayWithPrevious()) {
lowestInOneDayItemBottom = -1;
}
@ -953,7 +949,7 @@ void HistoryInner::performDrag() {
// urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o
//}
}
if (auto mimeData = mimeDataFromTextWithEntities(sel)) {
if (auto mimeData = MimeDataFromTextWithEntities(sel)) {
updateDragSelection(0, 0, false);
_widget->noSelectingScroll();
@ -1491,7 +1487,7 @@ void HistoryInner::copyContextText() {
}
void HistoryInner::setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode) {
if (auto data = mimeDataFromTextWithEntities(forClipboard)) {
if (auto data = MimeDataFromTextWithEntities(forClipboard)) {
QApplication::clipboard()->setMimeData(data.release(), mode);
}
}

View File

@ -269,11 +269,11 @@ private:
TopToBottom,
BottomToTop,
};
// 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
// 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
// if it returns false the enumeration stops immidiately
// Method has "bool (*Method)(HistoryItem *item, 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);
@ -289,19 +289,19 @@ private:
}
}
// this function finds all userpics on the left that are displayed and calls template method
// for each found userpic (from the top to the bottom) using enumerateItems() method
// This function finds all userpics on the left that are displayed and calls template method
// for each found userpic (from the top to the bottom) using enumerateItems() method.
//
// method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature
// if it returns false the enumeration stops immidiately
// Method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature
// if it returns false the enumeration stops immidiately.
template <typename Method>
void enumerateUserpics(Method method);
// this function finds all date elements that are displayed and calls template method
// for each found date element (from the bottom to the top) using enumerateItems() method
// This function finds all date elements that are displayed and calls template method
// for each found date element (from the bottom to the top) using enumerateItems() method.
//
// method has "bool (*Method)(HistoryItem *item, int itemtop, int dateTop)" signature
// if it returns false the enumeration stops immidiately
// Method has "bool (*Method)(HistoryItem *item, int itemtop, int dateTop)" signature
// if it returns false the enumeration stops immidiately.
template <typename Method>
void enumerateDates(Method method);

View File

@ -735,17 +735,22 @@ void HistoryItem::nextItemChanged() {
setAttachToNext(false);
}
bool HistoryItem::computeIsAttachToPrevious(gsl::not_null<HistoryItem*> previous) {
if (!Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
return !isPost() && !previous->isPost()
&& !serviceMsg() && !previous->serviceMsg()
&& !isEmpty() && !previous->isEmpty()
&& previous->from() == from()
&& (qAbs(previous->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
}
return false;
}
void HistoryItem::recountAttachToPrevious() {
Expects(!isLogEntry());
auto attachToPrevious = false;
if (auto previous = previousItem()) {
if (!Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
attachToPrevious = !isPost() && !previous->isPost()
&& !serviceMsg() && !previous->serviceMsg()
&& !isEmpty() && !previous->isEmpty()
&& previous->from() == from()
&& (qAbs(previous->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
}
attachToPrevious = computeIsAttachToPrevious(previous);
previous->setAttachToNext(attachToPrevious);
}
setAttachToPrevious(attachToPrevious);

View File

@ -880,13 +880,14 @@ public:
void clipCallback(Media::Clip::Notification notification);
void audioTrackUpdated();
bool computeIsAttachToPrevious(gsl::not_null<HistoryItem*> previous);
void setLogEntryDisplayDate(bool displayDate) {
Expects(isLogEntry());
setDisplayDate(displayDate);
}
void setLogEntryAttachToPrevious(bool attachToPrevious) {
Expects(isLogEntry());
setAttachToNext(attachToPrevious);
setAttachToPrevious(attachToPrevious);
}
void setLogEntryAttachToNext(bool attachToNext) {
Expects(isLogEntry());