tdesktop/Telegram/SourceFiles/dialogs/dialogs_widget.cpp

1578 lines
46 KiB
C++

/*
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 "dialogs/dialogs_widget.h"
#include "dialogs/dialogs_inner_widget.h"
#include "dialogs/dialogs_search_from_controllers.h"
#include "dialogs/dialogs_key.h"
#include "dialogs/dialogs_entry.h"
#include "history/history.h"
#include "history/feed/history_feed_section.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/effects/radial_animation.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "core/update_checker.h"
#include "auth_session.h"
#include "apiwrap.h"
#include "core/application.h"
#include "boxes/peer_list_box.h"
#include "boxes/peers/edit_participants_box.h"
#include "window/window_controller.h"
#include "window/window_slide_animation.h"
#include "window/window_connecting_widget.h"
#include "storage/storage_media_prepare.h"
#include "storage/localstorage.h"
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "styles/style_dialogs.h"
#include "styles/style_window.h"
namespace {
constexpr auto kDialogsFirstLoad = 20;
constexpr auto kDialogsPerPage = 500;
QString SwitchToChooseFromQuery() {
return qsl("from:");
}
} // namespace
class DialogsWidget::BottomButton : public Ui::RippleButton {
public:
BottomButton(
QWidget *parent,
const QString &text,
const style::FlatButton &st,
const style::icon &icon,
const style::icon &iconOver);
void setText(const QString &text);
protected:
void paintEvent(QPaintEvent *e) override;
void onStateChanged(State was, StateChangeSource source) override;
private:
void step_radial(crl::time ms, bool timer);
QString _text;
const style::FlatButton &_st;
const style::icon &_icon;
const style::icon &_iconOver;
std::unique_ptr<Ui::InfiniteRadialAnimation> _loading;
};
DialogsWidget::BottomButton::BottomButton(
QWidget *parent,
const QString &text,
const style::FlatButton &st,
const style::icon &icon,
const style::icon &iconOver)
: RippleButton(parent, st.ripple)
, _text(text.toUpper())
, _st(st)
, _icon(icon)
, _iconOver(iconOver) {
resize(st::columnMinimalWidthLeft, _st.height);
}
void DialogsWidget::BottomButton::setText(const QString &text) {
_text = text.toUpper();
update();
}
void DialogsWidget::BottomButton::step_radial(crl::time ms, bool timer) {
if (timer && !anim::Disabled() && width() < st::columnMinimalWidthLeft) {
update();
}
}
void DialogsWidget::BottomButton::onStateChanged(State was, StateChangeSource source) {
RippleButton::onStateChanged(was, source);
if ((was & StateFlag::Disabled) != (state() & StateFlag::Disabled)) {
_loading = isDisabled()
? std::make_unique<Ui::InfiniteRadialAnimation>(
animation(this, &BottomButton::step_radial),
st::dialogsLoadMoreLoading)
: nullptr;
if (_loading) {
_loading->start();
}
}
update();
}
void DialogsWidget::BottomButton::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto over = isOver() && !isDisabled();
QRect r(0, height() - _st.height, width(), _st.height);
p.fillRect(r, over ? _st.overBgColor : _st.bgColor);
if (!isDisabled()) {
paintRipple(p, 0, 0, crl::now());
}
p.setFont(over ? _st.overFont : _st.font);
p.setRenderHint(QPainter::TextAntialiasing);
p.setPen(over ? _st.overColor : _st.color);
if (width() >= st::columnMinimalWidthLeft) {
r.setTop(_st.textTop);
p.drawText(r, _text, style::al_top);
} else if (isDisabled() && _loading) {
_loading->draw(
p,
QPoint(
(width() - st::dialogsLoadMoreLoading.size.width()) / 2,
(height() - st::dialogsLoadMoreLoading.size.height()) / 2),
width());
} else {
(over ? _iconOver : _icon).paintInCenter(p, r);
}
}
DialogsWidget::DialogsWidget(QWidget *parent, not_null<Window::Controller*> controller) : Window::AbstractSectionWidget(parent, controller)
, _mainMenuToggle(this, st::dialogsMenuToggle)
, _filter(this, st::dialogsFilter, langFactory(lng_dlg_filter))
, _chooseFromUser(
this,
object_ptr<Ui::IconButton>(this, st::dialogsSearchFrom))
, _jumpToDate(
this,
object_ptr<Ui::IconButton>(this, st::dialogsCalendar))
, _cancelSearch(this, st::dialogsCancelSearch)
, _lockUnlock(this, st::dialogsLock)
, _scroll(this, st::dialogsScroll) {
_inner = _scroll->setOwnedWidget(object_ptr<DialogsInner>(this, controller, parent));
connect(_inner, SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int)));
connect(_inner, SIGNAL(mustScrollTo(int,int)), _scroll, SLOT(scrollToY(int,int)));
connect(_inner, SIGNAL(dialogMoved(int,int)), this, SLOT(onDialogMoved(int,int)));
connect(_inner, SIGNAL(searchMessages()), this, SLOT(onNeedSearchMessages()));
connect(_inner, SIGNAL(clearSearchQuery()), this, SLOT(onCancel()));
connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString)));
connect(_inner, SIGNAL(refreshHashtags()), this, SLOT(onFilterCursorMoved()));
connect(_inner, SIGNAL(cancelSearchInChat()), this, SLOT(onCancelSearchInChat()));
subscribe(_inner->searchFromUserChanged, [this](UserData *user) {
setSearchInChat(_searchInChat, user);
applyFilterUpdate(true);
});
connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged()));
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll()));
connect(_filter, &Ui::FlatInput::cancelled, [=] {
onCancel();
});
connect(_filter, &Ui::FlatInput::changed, [=] {
applyFilterUpdate();
});
connect(
_filter,
&Ui::FlatInput::cursorPositionChanged,
[=](int from, int to) { onFilterCursorMoved(from, to); });
if (!Core::UpdaterDisabled()) {
Core::UpdateChecker checker;
rpl::merge(
rpl::single(rpl::empty_value()),
checker.isLatest(),
checker.failed(),
checker.ready()
) | rpl::start_with_next([=] {
checkUpdateStatus();
}, lifetime());
}
subscribe(Adaptive::Changed(), [this] { updateForwardBar(); });
_cancelSearch->setClickedCallback([this] { onCancelSearch(); });
_jumpToDate->entity()->setClickedCallback([this] { showJumpToDate(); });
_chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); });
_lockUnlock->setVisible(Global::LocalPasscode());
subscribe(Global::RefLocalPasscodeChanged(), [this] { updateLockUnlockVisibility(); });
_lockUnlock->setClickedCallback([this] {
_lockUnlock->setIconOverride(&st::dialogsUnlockIcon, &st::dialogsUnlockIconOver);
Core::App().lockByPasscode();
_lockUnlock->setIconOverride(nullptr);
});
_mainMenuToggle->setClickedCallback([this] { showMainMenu(); });
_chooseByDragTimer.setSingleShot(true);
connect(&_chooseByDragTimer, SIGNAL(timeout()), this, SLOT(onChooseByDrag()));
setAcceptDrops(true);
_searchTimer.setSingleShot(true);
connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchMessages()));
_inner->setLoadMoreCallback([this] {
using State = DialogsInner::State;
const auto state = _inner->state();
if (state == State::Filtered && (!_inner->waitingForSearch()
|| (_searchInMigrated
&& _searchFull
&& !_searchFullMigrated))) {
onSearchMore();
} else {
loadDialogs();
}
});
_inner->listBottomReached(
) | rpl::start_with_next([=] {
loadMoreBlockedByDateChats();
}, lifetime());
_filter->setFocusPolicy(Qt::StrongFocus);
_filter->customUpDown(true);
updateJumpToDateVisibility(true);
updateSearchFromVisibility(true);
setupConnectingWidget();
setupSupportMode();
}
void DialogsWidget::setupConnectingWidget() {
_connecting = std::make_unique<Window::ConnectionState>(
this,
Window::AdaptiveIsOneColumn());
}
void DialogsWidget::setupSupportMode() {
if (!Auth().supportMode()) {
return;
}
Auth().settings().supportChatsTimeSliceValue(
) | rpl::start_with_next([=](int seconds) {
_dialogsLoadTill = seconds ? std::max(unixtime() - seconds, 0) : 0;
refreshLoadMoreButton();
}, lifetime());
Auth().settings().supportAllSearchResultsValue(
) | rpl::filter([=] {
return !_searchQuery.isEmpty();
}) | rpl::start_with_next([=] {
_searchTimer.stop();
_searchCache.clear();
_searchQueries.clear();
_searchQuery = QString();
_scroll->scrollToY(0);
onSearchMessages();
}, lifetime());
}
void DialogsWidget::checkUpdateStatus() {
Expects(!Core::UpdaterDisabled());
using Checker = Core::UpdateChecker;
if (Checker().state() == Checker::State::Ready) {
if (_updateTelegram) return;
_updateTelegram.create(
this,
lang(lng_update_telegram),
st::dialogsUpdateButton,
st::dialogsInstallUpdate,
st::dialogsInstallUpdateOver);
_updateTelegram->show();
_updateTelegram->setClickedCallback([] {
Core::checkReadyUpdate();
App::restart();
});
} else {
if (!_updateTelegram) return;
_updateTelegram.destroy();
}
updateControlsGeometry();
}
void DialogsWidget::activate() {
_filter->setFocus();
_inner->activate();
}
void DialogsWidget::createDialog(Dialogs::Key key) {
const auto creating = !key.entry()->inChatList(Dialogs::Mode::All);
_inner->createDialog(key);
const auto history = key.history();
if (creating && history && history->peer->migrateFrom()) {
if (const auto migrated = history->owner().historyLoaded(
history->peer->migrateFrom())) {
if (migrated->inChatList(Dialogs::Mode::All)) {
_inner->removeDialog(migrated);
}
}
}
}
void DialogsWidget::repaintDialogRow(
Dialogs::Mode list,
not_null<Dialogs::Row*> row) {
_inner->repaintDialogRow(list, row);
}
void DialogsWidget::repaintDialogRow(Dialogs::RowDescriptor row) {
_inner->repaintDialogRow(row);
}
void DialogsWidget::dialogsToUp() {
if (Auth().supportMode()) {
return;
}
if (_filter->getLastText().trimmed().isEmpty() && !_searchInChat) {
_scrollToAnimation.finish();
auto scrollTop = _scroll->scrollTop();
const auto scrollTo = 0;
const auto maxAnimatedDelta = _scroll->height();
if (scrollTo + maxAnimatedDelta < scrollTop) {
scrollTop = scrollTo + maxAnimatedDelta;
_scroll->scrollToY(scrollTop);
}
const auto scroll = [&] {
_scroll->scrollToY(qRound(_scrollToAnimation.current()));
};
_scrollToAnimation.start(
scroll,
scrollTop,
scrollTo,
st::slideDuration,
anim::sineInOut);
}
}
void DialogsWidget::startWidthAnimation() {
if (!_widthAnimationCache.isNull()) {
return;
}
auto scrollGeometry = _scroll->geometry();
auto grabGeometry = QRect(
scrollGeometry.x(),
scrollGeometry.y(),
st::columnMinimalWidthLeft,
scrollGeometry.height());
_scroll->setGeometry(grabGeometry);
Ui::SendPendingMoveResizeEvents(_scroll);
auto image = QImage(
grabGeometry.size() * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(cRetinaFactor());
image.fill(Qt::transparent);
_scroll->render(&image, QPoint(0, 0), QRect(QPoint(0, 0), grabGeometry.size()), QWidget::DrawChildren | QWidget::IgnoreMask);
_widthAnimationCache = App::pixmapFromImageInPlace(std::move(image));
_scroll->setGeometry(scrollGeometry);
_scroll->hide();
}
void DialogsWidget::stopWidthAnimation() {
_widthAnimationCache = QPixmap();
if (!_a_show.animating()) {
_scroll->show();
}
update();
}
void DialogsWidget::showFast() {
show();
updateForwardBar();
}
void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams &params) {
_showDirection = direction;
_a_show.finish();
_cacheUnder = params.oldContentCache;
show();
updateForwardBar();
_cacheOver = App::main()->grabForShowAnimation(params);
_scroll->hide();
_mainMenuToggle->hide();
if (_forwardCancel) _forwardCancel->hide();
_filter->hide();
_cancelSearch->hide(anim::type::instant);
_jumpToDate->hide(anim::type::instant);
_chooseFromUser->hide(anim::type::instant);
_lockUnlock->hide();
_connecting->setForceHidden(true);
int delta = st::slideShift;
if (_showDirection == Window::SlideDirection::FromLeft) {
std::swap(_cacheUnder, _cacheOver);
}
_a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition());
}
bool DialogsWidget::wheelEventFromFloatPlayer(QEvent *e) {
return _scroll->viewportEvent(e);
}
QRect DialogsWidget::rectForFloatPlayer() const {
return mapToGlobal(_scroll->geometry());
}
void DialogsWidget::animationCallback() {
update();
if (!_a_show.animating()) {
_cacheUnder = _cacheOver = QPixmap();
_scroll->show();
_mainMenuToggle->show();
if (_forwardCancel) _forwardCancel->show();
_filter->show();
_connecting->setForceHidden(false);
updateLockUnlockVisibility();
updateJumpToDateVisibility(true);
updateSearchFromVisibility(true);
applyFilterUpdate();
if (App::wnd()) App::wnd()->setInnerFocus();
}
}
void DialogsWidget::onCancel() {
if (!onCancelSearch() || (!_searchInChat && !App::main()->selectingPeer())) {
emit cancelled();
}
}
void DialogsWidget::notify_historyMuteUpdated(History *history) {
_inner->notify_historyMuteUpdated(history);
}
void DialogsWidget::dialogsReceived(
const MTPmessages_Dialogs &dialogs,
mtpRequestId requestId) {
if (_dialogsRequestId != requestId) return;
const auto [dialogsList, messagesList] = [&] {
const auto process = [&](const auto &data) {
Auth().data().processUsers(data.vusers);
Auth().data().processChats(data.vchats);
return std::make_tuple(&data.vdialogs.v, &data.vmessages.v);
};
switch (dialogs.type()) {
case mtpc_messages_dialogs:
_dialogsFull = true;
return process(dialogs.c_messages_dialogs());
case mtpc_messages_dialogsSlice:
return process(dialogs.c_messages_dialogsSlice());
}
Unexpected("Type in DialogsWidget::dialogsReceived");
}();
updateDialogsOffset(*dialogsList, *messagesList);
applyReceivedDialogs(*dialogsList, *messagesList);
_dialogsRequestId = 0;
loadDialogs();
if (!_dialogsRequestId) {
refreshLoadMoreButton();
}
Auth().data().moreChatsLoaded().notify();
if (_dialogsFull && _pinnedDialogsReceived) {
Auth().data().allChatsLoaded().set(true);
}
Auth().api().requestContacts();
}
void DialogsWidget::updateDialogsOffset(
const QVector<MTPDialog> &dialogs,
const QVector<MTPMessage> &messages) {
auto lastDate = TimeId(0);
auto lastPeer = PeerId(0);
auto lastMsgId = MsgId(0);
for (const auto &dialog : ranges::view::reverse(dialogs)) {
dialog.match([&](const auto &dialog) {
const auto peer = peerFromMTP(dialog.vpeer);
const auto messageId = dialog.vtop_message.v;
if (!peer || !messageId) {
return;
}
if (!lastPeer) {
lastPeer = peer;
}
if (!lastMsgId) {
lastMsgId = messageId;
}
for (const auto &message : ranges::view::reverse(messages)) {
if (IdFromMessage(message) == messageId
&& PeerFromMessage(message) == peer) {
if (const auto date = DateFromMessage(message)) {
lastDate = date;
}
return;
}
}
});
if (lastDate) {
break;
}
}
if (lastDate) {
_dialogsOffsetDate = lastDate;
_dialogsOffsetId = lastMsgId;
_dialogsOffsetPeer = Auth().data().peer(lastPeer);
} else {
_dialogsFull = true;
}
}
void DialogsWidget::refreshLoadMoreButton() {
if (_dialogsFull || !_dialogsLoadTill) {
_loadMoreChats.destroy();
updateControlsGeometry();
return;
}
if (!_loadMoreChats) {
_loadMoreChats.create(
this,
"Load more",
st::dialogsLoadMoreButton,
st::dialogsLoadMore,
st::dialogsLoadMore);
_loadMoreChats->addClickHandler([=] {
loadMoreBlockedByDateChats();
});
updateControlsGeometry();
}
const auto loading = !loadingBlockedByDate();
_loadMoreChats->setDisabled(loading);
_loadMoreChats->setText(loading ? "Loading..." : "Load more");
}
void DialogsWidget::loadMoreBlockedByDateChats() {
if (!_loadMoreChats
|| _loadMoreChats->isDisabled()
|| _loadMoreChats->isHidden()) {
return;
}
const auto max = Auth().settings().supportChatsTimeSlice();
_dialogsLoadTill = _dialogsOffsetDate
? (_dialogsOffsetDate - max)
: (unixtime() - max);
loadDialogs();
}
void DialogsWidget::pinnedDialogsReceived(
const MTPmessages_PeerDialogs &result,
mtpRequestId requestId) {
Expects(result.type() == mtpc_messages_peerDialogs);
if (_pinnedDialogsRequestId != requestId) return;
auto &data = result.c_messages_peerDialogs();
Auth().data().processUsers(data.vusers);
Auth().data().processChats(data.vchats);
Auth().data().applyPinnedDialogs(data.vdialogs.v);
applyReceivedDialogs(data.vdialogs.v, data.vmessages.v);
_pinnedDialogsRequestId = 0;
_pinnedDialogsReceived = true;
Auth().data().moreChatsLoaded().notify();
if (_dialogsFull && _pinnedDialogsReceived) {
Auth().data().allChatsLoaded().set(true);
}
}
void DialogsWidget::applyReceivedDialogs(
const QVector<MTPDialog> &dialogs,
const QVector<MTPMessage> &messages) {
App::feedMsgs(messages, NewMessageLast);
_inner->dialogsReceived(dialogs);
onListScroll();
}
bool DialogsWidget::dialogsFailed(const RPCError &error, mtpRequestId requestId) {
if (MTP::isDefaultHandledError(error)) return false;
LOG(("RPC Error: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description()));
if (_dialogsRequestId == requestId) {
_dialogsRequestId = 0;
} else if (_pinnedDialogsRequestId == requestId) {
_pinnedDialogsRequestId = 0;
}
return true;
}
void DialogsWidget::onDraggingScrollDelta(int delta) {
_draggingScrollDelta = _scroll ? delta : 0;
if (_draggingScrollDelta) {
if (!_draggingScrollTimer) {
_draggingScrollTimer.create(this);
_draggingScrollTimer->setSingleShot(false);
connect(_draggingScrollTimer, SIGNAL(timeout()), this, SLOT(onDraggingScrollTimer()));
}
_draggingScrollTimer->start(15);
} else {
_draggingScrollTimer.destroy();
}
}
void DialogsWidget::onDraggingScrollTimer() {
auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
_scroll->scrollToY(_scroll->scrollTop() + delta);
}
bool DialogsWidget::onSearchMessages(bool searchCache) {
auto result = false;
auto q = _filter->getLastText().trimmed();
if (q.isEmpty() && !_searchFromUser) {
MTP::cancel(base::take(_searchRequest));
MTP::cancel(base::take(_peerSearchRequest));
return true;
}
if (searchCache) {
const auto i = _searchCache.constFind(q);
if (i != _searchCache.cend()) {
_searchQuery = q;
_searchQueryFrom = _searchFromUser;
_searchFull = _searchFullMigrated = false;
MTP::cancel(base::take(_searchRequest));
searchReceived(
_searchInChat
? DialogsSearchPeerFromStart
: DialogsSearchFromStart,
i.value(),
0);
result = true;
}
} else if (_searchQuery != q || _searchQueryFrom != _searchFromUser) {
_searchQuery = q;
_searchQueryFrom = _searchFromUser;
_searchFull = _searchFullMigrated = false;
MTP::cancel(base::take(_searchRequest));
if (const auto peer = _searchInChat.peer()) {
const auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = MTP::send(
MTPmessages_Search(
flags,
peer->input,
MTP_string(_searchQuery),
_searchQueryFrom
? _searchQueryFrom->inputUser
: MTP_inputUserEmpty(),
MTP_inputMessagesFilterEmpty(),
MTP_int(0),
MTP_int(0),
MTP_int(0),
MTP_int(0),
MTP_int(SearchPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)),
rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart),
rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart));
} else if (const auto feed = _searchInChat.feed()) {
//_searchRequest = MTP::send( // #feed
// MTPchannels_SearchFeed(
// MTP_int(feed->id()),
// MTP_string(_searchQuery),
// MTP_int(0),
// MTP_inputPeerEmpty(),
// MTP_int(0),
// MTP_int(SearchPerPage)),
// rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart),
// rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart));
} else {
_searchRequest = MTP::send(
MTPmessages_SearchGlobal(
MTP_string(_searchQuery),
MTP_int(0),
MTP_inputPeerEmpty(),
MTP_int(0),
MTP_int(SearchPerPage)),
rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart),
rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart));
}
_searchQueries.insert(_searchRequest, _searchQuery);
}
if (searchForPeersRequired(q)) {
if (searchCache) {
auto i = _peerSearchCache.constFind(q);
if (i != _peerSearchCache.cend()) {
_peerSearchQuery = q;
_peerSearchRequest = 0;
peerSearchReceived(i.value(), 0);
result = true;
}
} else if (_peerSearchQuery != q) {
_peerSearchQuery = q;
_peerSearchFull = false;
_peerSearchRequest = MTP::send(
MTPcontacts_Search(
MTP_string(_peerSearchQuery),
MTP_int(SearchPeopleLimit)),
rpcDone(&DialogsWidget::peerSearchReceived),
rpcFail(&DialogsWidget::peopleFailed));
_peerSearchQueries.insert(_peerSearchRequest, _peerSearchQuery);
}
} else {
_peerSearchQuery = q;
_peerSearchFull = true;
peerSearchReceived(
MTP_contacts_found(
MTP_vector<MTPPeer>(0),
MTP_vector<MTPPeer>(0),
MTP_vector<MTPChat>(0),
MTP_vector<MTPUser>(0)),
0);
}
return result;
}
bool DialogsWidget::searchForPeersRequired(const QString &query) const {
if (_searchInChat || query.isEmpty()) {
return false;
}
return (query[0] != '#');
}
void DialogsWidget::onNeedSearchMessages() {
if (!onSearchMessages(true)) {
_searchTimer.start(AutoSearchTimeout);
}
}
void DialogsWidget::onChooseByDrag() {
_inner->chooseRow();
}
void DialogsWidget::showMainMenu() {
App::wnd()->showMainMenu();
}
void DialogsWidget::searchMessages(
const QString &query,
Dialogs::Key inChat) {
auto inChatChanged = [&] {
if (inChat == _searchInChat) {
return false;
} else if (const auto inPeer = inChat.peer()) {
if (inPeer->migrateTo() == _searchInChat.peer()) {
return false;
}
}
return true;
}();
if ((_filter->getLastText() != query) || inChatChanged) {
if (inChat) {
onCancelSearch();
setSearchInChat(inChat);
}
_filter->setText(query);
_filter->updatePlaceholder();
applyFilterUpdate(true);
_searchTimer.stop();
onSearchMessages();
Local::saveRecentSearchHashtags(query);
}
}
void DialogsWidget::onSearchMore() {
if (!_searchRequest) {
if (!_searchFull) {
auto offsetDate = _inner->lastSearchDate();
auto offsetPeer = _inner->lastSearchPeer();
auto offsetId = _inner->lastSearchId();
if (const auto peer = _searchInChat.peer()) {
auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = MTP::send(
MTPmessages_Search(
flags,
peer->input,
MTP_string(_searchQuery),
_searchQueryFrom
? _searchQueryFrom->inputUser
: MTP_inputUserEmpty(),
MTP_inputMessagesFilterEmpty(),
MTP_int(0),
MTP_int(0),
MTP_int(offsetId),
MTP_int(0),
MTP_int(SearchPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)),
rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart),
rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart));
} else if (const auto feed = _searchInChat.feed()) {
//_searchRequest = MTP::send( // #feed
// MTPchannels_SearchFeed(
// MTP_int(feed->id()),
// MTP_string(_searchQuery),
// MTP_int(offsetDate),
// offsetPeer
// ? offsetPeer->input
// : MTP_inputPeerEmpty(),
// MTP_int(offsetId),
// MTP_int(SearchPerPage)),
// rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart),
// rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart));
} else {
_searchRequest = MTP::send(
MTPmessages_SearchGlobal(
MTP_string(_searchQuery),
MTP_int(offsetDate),
offsetPeer
? offsetPeer->input
: MTP_inputPeerEmpty(),
MTP_int(offsetId),
MTP_int(SearchPerPage)),
rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart),
rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart));
}
if (!offsetId) {
_searchQueries.insert(_searchRequest, _searchQuery);
}
} else if (_searchInMigrated && !_searchFullMigrated) {
auto offsetMigratedId = _inner->lastSearchMigratedId();
auto flags = _searchQueryFrom
? MTP_flags(MTPmessages_Search::Flag::f_from_id)
: MTP_flags(0);
_searchRequest = MTP::send(
MTPmessages_Search(
flags,
_searchInMigrated->peer->input,
MTP_string(_searchQuery),
_searchQueryFrom
? _searchQueryFrom->inputUser
: MTP_inputUserEmpty(),
MTP_inputMessagesFilterEmpty(),
MTP_int(0),
MTP_int(0),
MTP_int(offsetMigratedId),
MTP_int(0),
MTP_int(SearchPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)),
rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart),
rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart));
}
}
}
bool DialogsWidget::loadingBlockedByDate() const {
return !_dialogsFull
&& !_dialogsRequestId
&& (_dialogsLoadTill > 0)
&& (_dialogsOffsetDate > 0)
&& (_dialogsOffsetDate <= _dialogsLoadTill);
}
void DialogsWidget::loadDialogs() {
if (_dialogsRequestId) return;
if (_dialogsFull) {
_inner->addAllSavedPeers();
return;
} else if (loadingBlockedByDate()) {
return;
}
const auto firstLoad = !_dialogsOffsetDate;
const auto loadCount = firstLoad ? kDialogsFirstLoad : kDialogsPerPage;
const auto flags = MTPmessages_GetDialogs::Flag::f_exclude_pinned;
const auto feedId = 0;
const auto hash = 0;
_dialogsRequestId = MTP::send(
MTPmessages_GetDialogs(
MTP_flags(flags),
//MTP_int(feedId), // #feed
MTP_int(_dialogsOffsetDate),
MTP_int(_dialogsOffsetId),
_dialogsOffsetPeer
? _dialogsOffsetPeer->input
: MTP_inputPeerEmpty(),
MTP_int(loadCount),
MTP_int(hash)),
rpcDone(&DialogsWidget::dialogsReceived),
rpcFail(&DialogsWidget::dialogsFailed));
if (!_pinnedDialogsReceived) {
loadPinnedDialogs();
}
refreshLoadMoreButton();
}
void DialogsWidget::loadPinnedDialogs() {
if (_pinnedDialogsRequestId) return;
_pinnedDialogsReceived = false;
_pinnedDialogsRequestId = MTP::send(MTPmessages_GetPinnedDialogs(), rpcDone(&DialogsWidget::pinnedDialogsReceived), rpcFail(&DialogsWidget::dialogsFailed));
}
void DialogsWidget::searchReceived(
DialogsSearchRequestType type,
const MTPmessages_Messages &result,
mtpRequestId requestId) {
using State = DialogsInner::State;
const auto state = _inner->state();
if (state == State::Filtered) {
if (type == DialogsSearchFromStart || type == DialogsSearchPeerFromStart) {
auto i = _searchQueries.find(requestId);
if (i != _searchQueries.cend()) {
_searchCache[i.value()] = result;
_searchQueries.erase(i);
}
}
}
if (_searchRequest == requestId) {
switch (result.type()) {
case mtpc_messages_messages: {
auto &d = result.c_messages_messages();
if (_searchRequest != 0) {
// Don't apply cached data!
Auth().data().processUsers(d.vusers);
Auth().data().processChats(d.vchats);
}
auto &msgs = d.vmessages.v;
if (!_inner->searchReceived(msgs, type, msgs.size())) {
if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
}
} break;
case mtpc_messages_messagesSlice: {
auto &d = result.c_messages_messagesSlice();
if (_searchRequest != 0) {
// Don't apply cached data!
Auth().data().processUsers(d.vusers);
Auth().data().processChats(d.vchats);
}
auto &msgs = d.vmessages.v;
if (!_inner->searchReceived(msgs, type, d.vcount.v)) {
if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
}
} break;
case mtpc_messages_channelMessages: {
auto &d = result.c_messages_channelMessages();
if (const auto peer = _searchInChat.peer()) {
if (const auto channel = peer->asChannel()) {
channel->ptsReceived(d.vpts.v);
} else {
LOG(("API Error: "
"received messages.channelMessages when no channel "
"was passed! (DialogsWidget::searchReceived)"));
}
} else {
LOG(("API Error: "
"received messages.channelMessages when no channel "
"was passed! (DialogsWidget::searchReceived)"));
}
if (_searchRequest != 0) {
// Don't apply cached data!
Auth().data().processUsers(d.vusers);
Auth().data().processChats(d.vchats);
}
auto &msgs = d.vmessages.v;
if (!_inner->searchReceived(msgs, type, d.vcount.v)) {
if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
}
} break;
case mtpc_messages_messagesNotModified: {
LOG(("API Error: received messages.messagesNotModified! (DialogsWidget::searchReceived)"));
if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
} break;
}
_searchRequest = 0;
onListScroll();
update();
}
}
void DialogsWidget::peerSearchReceived(
const MTPcontacts_Found &result,
mtpRequestId requestId) {
using State = DialogsInner::State;
const auto state = _inner->state();
auto q = _peerSearchQuery;
if (state == State::Filtered) {
auto i = _peerSearchQueries.find(requestId);
if (i != _peerSearchQueries.cend()) {
q = i.value();
_peerSearchCache[q] = result;
_peerSearchQueries.erase(i);
}
}
if (_peerSearchRequest == requestId) {
switch (result.type()) {
case mtpc_contacts_found: {
auto &d = result.c_contacts_found();
Auth().data().processUsers(d.vusers);
Auth().data().processChats(d.vchats);
_inner->peerSearchReceived(q, d.vmy_results.v, d.vresults.v);
} break;
}
_peerSearchRequest = 0;
onListScroll();
}
}
bool DialogsWidget::searchFailed(
DialogsSearchRequestType type,
const RPCError &error,
mtpRequestId requestId) {
if (MTP::isDefaultHandledError(error)) return false;
if (_searchRequest == requestId) {
_searchRequest = 0;
if (type == DialogsSearchMigratedFromStart || type == DialogsSearchMigratedFromOffset) {
_searchFullMigrated = true;
} else {
_searchFull = true;
}
}
return true;
}
bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) {
if (MTP::isDefaultHandledError(error)) return false;
if (_peerSearchRequest == req) {
_peerSearchRequest = 0;
_peerSearchFull = true;
}
return true;
}
void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) {
using namespace Storage;
if (App::main()->selectingPeer()) return;
const auto data = e->mimeData();
_dragInScroll = false;
_dragForward = Adaptive::OneColumn()
? false
: data->hasFormat(qsl("application/x-td-forward"));
if (_dragForward) {
e->setDropAction(Qt::CopyAction);
e->accept();
updateDragInScroll(_scroll->geometry().contains(e->pos()));
} else if (ComputeMimeDataState(data) != MimeDataState::None) {
e->setDropAction(Qt::CopyAction);
e->accept();
}
_chooseByDragTimer.stop();
}
void DialogsWidget::dragMoveEvent(QDragMoveEvent *e) {
if (_scroll->geometry().contains(e->pos())) {
if (_dragForward) {
updateDragInScroll(true);
} else {
_chooseByDragTimer.start(ChoosePeerByDragTimeout);
}
if (_inner->updateFromParentDrag(mapToGlobal(e->pos()))) {
e->setDropAction(Qt::CopyAction);
} else {
e->setDropAction(Qt::IgnoreAction);
}
} else {
if (_dragForward) updateDragInScroll(false);
_inner->dragLeft();
e->setDropAction(Qt::IgnoreAction);
}
e->accept();
}
void DialogsWidget::dragLeaveEvent(QDragLeaveEvent *e) {
if (_dragForward) {
updateDragInScroll(false);
} else {
_chooseByDragTimer.stop();
}
_inner->dragLeft();
e->accept();
}
void DialogsWidget::updateDragInScroll(bool inScroll) {
if (_dragInScroll != inScroll) {
_dragInScroll = inScroll;
if (_dragInScroll) {
App::main()->showForwardLayer({});
} else {
App::main()->dialogsCancelled();
}
}
}
void DialogsWidget::dropEvent(QDropEvent *e) {
_chooseByDragTimer.stop();
if (_scroll->geometry().contains(e->pos())) {
if (auto peer = _inner->updateFromParentDrag(mapToGlobal(e->pos()))) {
e->acceptProposedAction();
App::main()->onFilesOrForwardDrop(peer->id, e->mimeData());
controller()->window()->activateWindow();
}
}
}
void DialogsWidget::onListScroll() {
auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
}
void DialogsWidget::applyFilterUpdate(bool force) {
if (_a_show.animating() && !force) {
return;
}
auto filterText = _filter->getLastText();
_inner->applyFilterUpdate(filterText, force);
if (filterText.isEmpty() && !_searchFromUser) {
clearSearchCache();
}
_cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal);
if (_loadMoreChats) {
_loadMoreChats->setVisible(filterText.isEmpty());
updateControlsGeometry();
}
updateJumpToDateVisibility();
if (filterText.isEmpty()) {
_peerSearchCache.clear();
_peerSearchQueries.clear();
_peerSearchQuery = QString();
}
if (_chooseFromUser->toggled() || _searchFromUser) {
auto switchToChooseFrom = SwitchToChooseFromQuery();
if (_lastFilterText != switchToChooseFrom
&& switchToChooseFrom.startsWith(_lastFilterText)
&& filterText == switchToChooseFrom) {
showSearchFrom();
}
}
_lastFilterText = filterText;
}
void DialogsWidget::searchInChat(Dialogs::Key chat) {
onCancelSearch();
setSearchInChat(chat);
applyFilterUpdate(true);
}
void DialogsWidget::setSearchInChat(Dialogs::Key chat, UserData *from) {
_searchInMigrated = nullptr;
if (const auto peer = chat.peer()) {
if (const auto migrateTo = peer->migrateTo()) {
return setSearchInChat(peer->owner().history(migrateTo), from);
} else if (const auto migrateFrom = peer->migrateFrom()) {
_searchInMigrated = peer->owner().history(migrateFrom);
}
}
const auto searchInPeerUpdated = (_searchInChat != chat);
if (searchInPeerUpdated) {
_searchInChat = chat;
from = nullptr;
controller()->searchInChat = _searchInChat;
updateJumpToDateVisibility();
} else if (!_searchInChat) {
from = nullptr;
}
if (_searchFromUser != from || searchInPeerUpdated) {
_searchFromUser = from;
updateSearchFromVisibility();
clearSearchCache();
}
_inner->searchInChat(_searchInChat, _searchFromUser);
if (_searchFromUser && _lastFilterText == SwitchToChooseFromQuery()) {
onCancelSearch();
}
_filter->setFocus();
}
void DialogsWidget::clearSearchCache() {
_searchCache.clear();
_searchQueries.clear();
_searchQuery = QString();
_searchQueryFrom = nullptr;
MTP::cancel(base::take(_searchRequest));
}
void DialogsWidget::showJumpToDate() {
if (_searchInChat) {
this->controller()->showJumpToDate(_searchInChat, QDate());
}
}
void DialogsWidget::showSearchFrom() {
if (const auto peer = _searchInChat.peer()) {
const auto chat = _searchInChat;
Dialogs::ShowSearchFromBox(
controller(),
peer,
crl::guard(this, [=](not_null<UserData*> user) {
Ui::hideLayer();
setSearchInChat(chat, user);
applyFilterUpdate(true);
}),
crl::guard(this, [=] { _filter->setFocus(); }));
}
}
void DialogsWidget::onFilterCursorMoved(int from, int to) {
if (to < 0) to = _filter->cursorPosition();
QString t = _filter->getLastText();
QStringRef r;
for (int start = to; start > 0;) {
--start;
if (t.size() <= start) break;
if (t.at(start) == '#') {
r = t.midRef(start, to - start);
break;
}
if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') break;
}
_inner->onHashtagFilterUpdate(r);
}
void DialogsWidget::onCompleteHashtag(QString tag) {
QString t = _filter->getLastText(), r;
int cur = _filter->cursorPosition();
for (int start = cur; start > 0;) {
--start;
if (t.size() <= start) break;
if (t.at(start) == '#') {
if (cur == start + 1 || t.midRef(start + 1, cur - start - 1) == tag.midRef(0, cur - start - 1)) {
for (; cur < t.size() && cur - start - 1 < tag.size(); ++cur) {
if (t.at(cur) != tag.at(cur - start - 1)) break;
}
if (cur - start - 1 == tag.size() && cur < t.size() && t.at(cur) == ' ') ++cur;
r = t.mid(0, start + 1) + tag + ' ' + t.mid(cur);
_filter->setText(r);
_filter->setCursorPosition(start + 1 + tag.size() + 1);
applyFilterUpdate(true);
return;
}
break;
}
if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') break;
}
_filter->setText(t.mid(0, cur) + '#' + tag + ' ' + t.mid(cur));
_filter->setCursorPosition(cur + 1 + tag.size() + 1);
applyFilterUpdate(true);
}
void DialogsWidget::resizeEvent(QResizeEvent *e) {
updateControlsGeometry();
}
void DialogsWidget::updateLockUnlockVisibility() {
if (!_a_show.animating()) {
_lockUnlock->setVisible(Global::LocalPasscode());
}
updateControlsGeometry();
}
void DialogsWidget::updateJumpToDateVisibility(bool fast) {
if (_a_show.animating()) return;
_jumpToDate->toggle(
(_searchInChat && _filter->getLastText().isEmpty()),
fast ? anim::type::instant : anim::type::normal);
}
void DialogsWidget::updateSearchFromVisibility(bool fast) {
auto visible = [&] {
if (const auto peer = _searchInChat.peer()) {
if (peer->isChat() || peer->isMegagroup()) {
return !_searchFromUser;
}
}
return false;
}();
auto changed = (visible == !_chooseFromUser->toggled());
_chooseFromUser->toggle(
visible,
fast ? anim::type::instant : anim::type::normal);
if (changed) {
auto margins = st::dialogsFilter.textMrg;
if (visible) {
margins.setRight(margins.right() + _chooseFromUser->width());
}
_filter->setTextMrg(margins);
}
}
void DialogsWidget::updateControlsGeometry() {
auto filterAreaTop = 0;
if (_forwardCancel) {
_forwardCancel->moveToLeft(0, filterAreaTop);
filterAreaTop += st::dialogsForwardHeight;
}
auto smallLayoutWidth = (st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPadding.x());
auto smallLayoutRatio = (width() < st::columnMinimalWidthLeft) ? (st::columnMinimalWidthLeft - width()) / float64(st::columnMinimalWidthLeft - smallLayoutWidth) : 0.;
auto filterLeft = st::dialogsFilterPadding.x() + _mainMenuToggle->width() + st::dialogsFilterPadding.x();
auto filterRight = (Global::LocalPasscode() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x();
auto filterWidth = qMax(width(), st::columnMinimalWidthLeft) - filterLeft - filterRight;
auto filterAreaHeight = st::dialogsFilterPadding.y() + _mainMenuToggle->height() + st::dialogsFilterPadding.y();
auto filterTop = filterAreaTop + (filterAreaHeight - _filter->height()) / 2;
filterLeft = anim::interpolate(filterLeft, smallLayoutWidth, smallLayoutRatio);
_filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height());
auto mainMenuLeft = anim::interpolate(st::dialogsFilterPadding.x(), (smallLayoutWidth - _mainMenuToggle->width()) / 2, smallLayoutRatio);
_mainMenuToggle->moveToLeft(mainMenuLeft, filterAreaTop + st::dialogsFilterPadding.y());
auto right = filterLeft + filterWidth;
_lockUnlock->moveToLeft(right + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y());
_cancelSearch->moveToLeft(right - _cancelSearch->width(), _filter->y());
right -= _jumpToDate->width(); _jumpToDate->moveToLeft(right, _filter->y());
right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _filter->y());
auto scrollTop = filterAreaTop + filterAreaHeight;
auto addToScroll = App::main() ? App::main()->contentScrollAddToY() : 0;
auto newScrollTop = _scroll->scrollTop() + addToScroll;
auto scrollHeight = height() - scrollTop;
const auto putBottomButton = [&](object_ptr<BottomButton> &button) {
if (button && !button->isHidden()) {
const auto buttonHeight = button->height();
scrollHeight -= buttonHeight;
button->setGeometry(
0,
scrollTop + scrollHeight,
width(),
buttonHeight);
}
};
putBottomButton(_updateTelegram);
putBottomButton(_loadMoreChats);
auto wasScrollHeight = _scroll->height();
_scroll->setGeometry(0, scrollTop, width(), scrollHeight);
if (scrollHeight != wasScrollHeight) {
controller()->floatPlayerAreaUpdated().notify(true);
}
if (addToScroll) {
_scroll->scrollToY(newScrollTop);
} else {
onListScroll();
}
}
void DialogsWidget::updateForwardBar() {
auto selecting = App::main()->selectingPeer();
auto oneColumnSelecting = (Adaptive::OneColumn() && selecting);
if (!oneColumnSelecting == !_forwardCancel) {
return;
}
if (oneColumnSelecting) {
_forwardCancel.create(this, st::dialogsForwardCancel);
_forwardCancel->setClickedCallback([] { Global::RefPeerChooseCancel().notify(true); });
if (!_a_show.animating()) _forwardCancel->show();
} else {
_forwardCancel.destroyDelayed();
}
updateControlsGeometry();
update();
}
void DialogsWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
e->ignore();
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
if (!_inner->chooseRow()) {
using State = DialogsInner::State;
const auto state = _inner->state();
if (state == State::Default
|| (state == State::Filtered
&& (!_inner->waitingForSearch() || _inner->hasFilteredResults()))) {
_inner->selectSkip(1);
_inner->chooseRow();
} else {
onSearchMessages();
}
}
} else if (e->key() == Qt::Key_Down) {
_inner->selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_inner->selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_inner->selectSkipPage(_scroll->height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_inner->selectSkipPage(_scroll->height(), -1);
} else {
e->ignore();
}
}
void DialogsWidget::paintEvent(QPaintEvent *e) {
if (App::wnd() && App::wnd()->contentOverlapped(this, e)) return;
Painter p(this);
QRect r(e->rect());
if (r != rect()) {
p.setClipRect(r);
}
auto progress = _a_show.current(crl::now(), 1.);
if (_a_show.animating()) {
auto retina = cIntRetinaFactor();
auto fromLeft = (_showDirection == Window::SlideDirection::FromLeft);
auto coordUnder = fromLeft ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress);
auto coordOver = fromLeft ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress);
auto shadow = fromLeft ? (1. - progress) : progress;
if (coordOver > 0) {
p.drawPixmap(QRect(0, 0, coordOver, _cacheUnder.height() / retina), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, _cacheUnder.height()));
p.setOpacity(shadow);
p.fillRect(0, 0, coordOver, _cacheUnder.height() / retina, st::slideFadeOutBg);
p.setOpacity(1);
}
p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, _cacheOver.height() / retina), _cacheOver, QRect(0, 0, _cacheOver.width(), _cacheOver.height()));
p.setOpacity(shadow);
st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), _cacheOver.height() / retina));
return;
}
auto aboveTop = 0;
if (_forwardCancel) {
p.fillRect(0, aboveTop, width(), st::dialogsForwardHeight, st::dialogsForwardBg);
p.setPen(st::dialogsForwardFg);
p.setFont(st::dialogsForwardFont);
p.drawTextLeft(st::dialogsForwardTextLeft, st::dialogsForwardTextTop, width(), lang(lng_forward_choose));
aboveTop += st::dialogsForwardHeight;
}
auto above = QRect(0, aboveTop, width(), _scroll->y() - aboveTop);
if (above.intersects(r)) {
p.fillRect(above.intersected(r), st::dialogsBg);
}
auto belowTop = _scroll->y() + qMin(_scroll->height(), _inner->height());
if (!_widthAnimationCache.isNull()) {
p.drawPixmapLeft(0, _scroll->y(), width(), _widthAnimationCache);
belowTop = _scroll->y() + (_widthAnimationCache.height() / cIntRetinaFactor());
}
auto below = QRect(0, belowTop, width(), height() - belowTop);
if (below.intersects(r)) {
p.fillRect(below.intersected(r), st::dialogsBg);
}
}
void DialogsWidget::destroyData() {
_inner->destroyData();
}
void DialogsWidget::scrollToEntry(const Dialogs::RowDescriptor &entry) {
_inner->scrollToEntry(entry);
}
void DialogsWidget::removeDialog(Dialogs::Key key) {
_inner->removeDialog(key);
}
Dialogs::IndexedList *DialogsWidget::contactsList() {
return _inner->contactsList();
}
Dialogs::IndexedList *DialogsWidget::dialogsList() {
return _inner->dialogsList();
}
Dialogs::IndexedList *DialogsWidget::contactsNoDialogsList() {
return _inner->contactsNoDialogsList();
}
bool DialogsWidget::onCancelSearch() {
bool clearing = !_filter->getLastText().isEmpty();
if (_searchRequest) {
MTP::cancel(_searchRequest);
_searchRequest = 0;
}
if (_searchInChat && !clearing) {
if (Adaptive::OneColumn()) {
if (const auto peer = _searchInChat.peer()) {
Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
} else if (const auto feed = _searchInChat.feed()) {
controller()->showSection(HistoryFeed::Memento(feed));
} else {
Unexpected("Empty key in onCancelSearch().");
}
}
setSearchInChat(Dialogs::Key());
clearing = true;
}
_inner->clearFilter();
_filter->clear();
_filter->updatePlaceholder();
applyFilterUpdate();
return clearing;
}
void DialogsWidget::onCancelSearchInChat() {
if (_searchRequest) {
MTP::cancel(_searchRequest);
_searchRequest = 0;
}
if (_searchInChat) {
if (Adaptive::OneColumn() && !App::main()->selectingPeer()) {
if (const auto peer = _searchInChat.peer()) {
Ui::showPeerHistory(peer, ShowAtUnreadMsgId);
} else if (const auto feed = _searchInChat.feed()) {
controller()->showSection(HistoryFeed::Memento(feed));
} else {
Unexpected("Empty key in onCancelSearchInPeer().");
}
}
setSearchInChat(Dialogs::Key());
}
_inner->clearFilter();
_filter->clear();
_filter->updatePlaceholder();
applyFilterUpdate();
if (!Adaptive::OneColumn() && !App::main()->selectingPeer()) {
emit cancelled();
}
}
void DialogsWidget::onDialogMoved(int movedFrom, int movedTo) {
int32 st = _scroll->scrollTop();
if (st > movedTo && st < movedFrom) {
_scroll->scrollToY(st + st::dialogsRowHeight);
}
}
DialogsWidget::~DialogsWidget() = default;