tdesktop/Telegram/SourceFiles/boxes/peer_lists_box.cpp

439 lines
11 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 "boxes/peer_lists_box.h"
#include "lang/lang_keys.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "main/session/session_show.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_peer.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
PeerListsBox::PeerListsBox(
QWidget*,
std::vector<std::unique_ptr<PeerListController>> controllers,
Fn<void(not_null<PeerListsBox*>)> init)
: _lists(makeLists(std::move(controllers)))
, _init(std::move(init)) {
Expects(!_lists.empty());
}
auto PeerListsBox::collectSelectedRows()
-> std::vector<not_null<PeerData*>> {
auto result = std::vector<not_null<PeerData*>>();
auto items = _select
? _select->entity()->getItems()
: QVector<uint64>();
if (!items.empty()) {
result.reserve(items.size());
const auto session = &firstController()->session();
for (const auto itemId : items) {
const auto foreign = [&] {
for (const auto &list : _lists) {
if (list.controller->isForeignRow(itemId)) {
return true;
}
}
return false;
}();
if (!foreign) {
result.push_back(session->data().peer(PeerId(itemId)));
}
}
}
return result;
}
PeerListsBox::List PeerListsBox::makeList(
std::unique_ptr<PeerListController> controller) {
auto delegate = std::make_unique<Delegate>(this, controller.get());
return {
std::move(controller),
std::move(delegate),
};
}
std::vector<PeerListsBox::List> PeerListsBox::makeLists(
std::vector<std::unique_ptr<PeerListController>> controllers) {
auto result = std::vector<List>();
result.reserve(controllers.size());
for (auto &controller : controllers) {
result.push_back(makeList(std::move(controller)));
}
return result;
}
not_null<PeerListController*> PeerListsBox::firstController() const {
return _lists.front().controller.get();
}
void PeerListsBox::createMultiSelect() {
Expects(_select == nullptr);
auto entity = object_ptr<Ui::MultiSelect>(
this,
(firstController()->selectSt()
? *firstController()->selectSt()
: st::defaultMultiSelect),
tr::lng_participant_filter());
_select.create(this, std::move(entity));
_select->heightValue(
) | rpl::start_with_next(
[this] { updateScrollSkips(); },
lifetime());
_select->entity()->setSubmittedCallback([=](Qt::KeyboardModifiers) {
for (const auto &list : _lists) {
if (list.content->submitted()) {
break;
}
}
});
_select->entity()->setQueryChangedCallback([=](const QString &query) {
searchQueryChanged(query);
});
_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
for (const auto &list : _lists) {
if (list.controller->handleDeselectForeignRow(itemId)) {
return;
}
}
const auto session = &firstController()->session();
if (const auto peer = session->data().peerLoaded(PeerId(itemId))) {
const auto id = peer->id;
for (const auto &list : _lists) {
if (const auto row = list.delegate->peerListFindRow(id.value)) {
list.content->changeCheckState(
row,
false,
anim::type::normal);
update();
}
list.controller->itemDeselectedHook(peer);
}
}
});
_select->resizeToWidth(firstController()->contentWidth());
_select->moveToLeft(0, 0);
}
int PeerListsBox::getTopScrollSkip() const {
auto result = 0;
if (_select && !_select->isHidden()) {
result += _select->height();
}
return result;
}
void PeerListsBox::updateScrollSkips() {
// If we show / hide the search field scroll top is fixed.
// If we resize search field by bubbles scroll bottom is fixed.
setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed);
if (!_select->animating()) {
_scrollBottomFixed = true;
}
}
void PeerListsBox::prepare() {
auto rows = setInnerWidget(
object_ptr<Ui::VerticalLayout>(this),
st::boxScroll);
for (auto &list : _lists) {
const auto content = rows->add(object_ptr<PeerListContent>(
rows,
list.controller.get()));
list.content = content;
list.delegate->setContent(content);
list.controller->setDelegate(list.delegate.get());
content->scrollToRequests(
) | rpl::start_with_next([=](Ui::ScrollToRequest request) {
const auto skip = content->y();
scrollToY(
skip + request.ymin,
(request.ymax >= 0) ? (skip + request.ymax) : request.ymax);
}, lifetime());
content->selectedIndexValue(
) | rpl::filter([=](int index) {
return (index >= 0);
}) | rpl::start_with_next([=] {
for (const auto &list : _lists) {
if (list.content && list.content != content) {
list.content->clearSelection();
}
}
}, lifetime());
}
rows->resizeToWidth(firstController()->contentWidth());
setDimensions(firstController()->contentWidth(), st::boxMaxListHeight);
if (_select) {
_select->finishAnimating();
Ui::SendPendingMoveResizeEvents(_select);
_scrollBottomFixed = true;
scrollToY(0);
}
if (_init) {
_init(this);
}
}
void PeerListsBox::keyPressEvent(QKeyEvent *e) {
const auto skipRows = [&](int rows) {
if (rows == 0) {
return;
}
for (const auto &list : _lists) {
if (list.content->hasPressed()) {
return;
}
}
const auto from = begin(_lists), till = end(_lists);
auto i = from;
for (; i != till; ++i) {
if (i->content->hasSelection()) {
break;
}
}
if (i == till && rows < 0) {
return;
}
if (rows > 0) {
if (i == till) {
i = from;
}
for (; i != till; ++i) {
const auto result = i->content->selectSkip(rows);
if (result.shouldMoveTo - result.reallyMovedTo >= rows) {
continue;
} else if (result.reallyMovedTo >= result.shouldMoveTo) {
return;
} else {
rows = result.shouldMoveTo - result.reallyMovedTo;
}
}
} else {
for (++i; i != from;) {
const auto result = (--i)->content->selectSkip(rows);
if (result.shouldMoveTo - result.reallyMovedTo <= rows) {
continue;
} else if (result.reallyMovedTo <= result.shouldMoveTo) {
return;
} else {
rows = result.shouldMoveTo - result.reallyMovedTo;
}
}
}
};
const auto rowsInPage = [&] {
const auto rowHeight = firstController()->computeListSt().item.height;
return height() / rowHeight;
};
if (e->key() == Qt::Key_Down) {
skipRows(1);
} else if (e->key() == Qt::Key_Up) {
skipRows(-1);
} else if (e->key() == Qt::Key_PageDown) {
skipRows(rowsInPage());
} else if (e->key() == Qt::Key_PageUp) {
skipRows(-rowsInPage());
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
_select->entity()->clearQuery();
} else {
BoxContent::keyPressEvent(e);
}
}
void PeerListsBox::searchQueryChanged(const QString &query) {
scrollToY(0);
for (const auto &list : _lists) {
list.content->searchQueryChanged(query);
}
}
void PeerListsBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
if (_select) {
_select->resizeToWidth(width());
_select->moveToLeft(0, 0);
updateScrollSkips();
}
for (const auto &list : _lists) {
list.content->resizeToWidth(width());
}
}
void PeerListsBox::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto &bg = (firstController()->listSt()
? *firstController()->listSt()
: st::peerListBox).bg;
for (const auto &rect : e->region()) {
p.fillRect(rect, bg);
}
}
void PeerListsBox::setInnerFocus() {
if (!_select || !_select->toggled()) {
_lists.front().content->setFocus();
} else {
_select->entity()->setInnerFocus();
}
}
PeerListsBox::Delegate::Delegate(
not_null<PeerListsBox*> box,
not_null<PeerListController*> controller)
: _box(box)
, _controller(controller)
, _show(Main::MakeSessionShow(_box->uiShow(), &_controller->session())) {
}
void PeerListsBox::Delegate::peerListSetTitle(rpl::producer<QString> title) {
}
void PeerListsBox::Delegate::peerListSetAdditionalTitle(
rpl::producer<QString> title) {
}
void PeerListsBox::Delegate::peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) {
if (checked) {
_box->addSelectItem(row, anim::type::normal);
PeerListContentDelegate::peerListSetRowChecked(row, checked);
peerListUpdateRow(row);
// This call deletes row from _searchRows.
_box->_select->entity()->clearQuery();
} else {
// The itemRemovedCallback will call changeCheckState() here.
_box->_select->entity()->removeItem(row->id());
peerListUpdateRow(row);
}
}
void PeerListsBox::Delegate::peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) {
if (checked) {
_box->addSelectItem(row, animated);
// This call deletes row from _searchRows.
_box->_select->entity()->clearQuery();
} else {
// The itemRemovedCallback will call changeCheckState() here.
_box->_select->entity()->removeItem(row->id());
}
}
void PeerListsBox::Delegate::peerListScrollToTop() {
_box->scrollToY(0);
}
void PeerListsBox::Delegate::peerListSetSearchMode(PeerListSearchMode mode) {
PeerListContentDelegate::peerListSetSearchMode(mode);
_box->setSearchMode(mode);
}
void PeerListsBox::setSearchMode(PeerListSearchMode mode) {
auto selectVisible = (mode != PeerListSearchMode::Disabled);
if (selectVisible && !_select) {
createMultiSelect();
_select->toggle(!selectVisible, anim::type::instant);
}
if (_select) {
_select->toggle(selectVisible, anim::type::normal);
_scrollBottomFixed = false;
setInnerFocus();
}
}
void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() {
Expects(_box->_select != nullptr);
_box->_select->entity()->finishItemsBunch();
}
auto PeerListsBox::Delegate::peerListUiShow()
-> std::shared_ptr<Main::SessionShow> {
return _show;
}
bool PeerListsBox::Delegate::peerListIsRowChecked(
not_null<PeerListRow*> row) {
return _box->_select
? _box->_select->entity()->hasItem(row->id())
: false;
}
int PeerListsBox::Delegate::peerListSelectedRowsCount() {
return _box->_select ? _box->_select->entity()->getItemsCount() : 0;
}
void PeerListsBox::addSelectItem(
not_null<PeerData*> peer,
anim::type animated) {
addSelectItem(
peer->id.value,
peer->shortName(),
(peer->isForum()
? ForceRoundUserpicCallback(peer)
: PaintUserpicCallback(peer, false)),
animated);
}
void PeerListsBox::addSelectItem(
not_null<PeerListRow*> row,
anim::type animated) {
addSelectItem(
row->id(),
row->generateShortName(),
row->generatePaintUserpicCallback(true),
animated);
}
void PeerListsBox::addSelectItem(
uint64 itemId,
const QString &text,
Ui::MultiSelect::PaintRoundImage paintUserpic,
anim::type animated) {
if (!_select) {
createMultiSelect();
_select->hide(anim::type::instant);
}
const auto &activeBg = (firstController()->selectSt()
? *firstController()->selectSt()
: st::defaultMultiSelect).item.textActiveBg;
if (animated == anim::type::instant) {
_select->entity()->addItemInBunch(
itemId,
text,
activeBg,
std::move(paintUserpic));
} else {
_select->entity()->addItem(
itemId,
text,
activeBg,
std::move(paintUserpic));
}
}