mirror of
https://github.com/telegramdesktop/tdesktop
synced 2024-12-28 01:23:09 +00:00
Support userpics and dates in channel log events.
This commit is contained in:
parent
5c87b42135
commit
0a39e7e2b1
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user