2017-03-13 15:40:21 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2017-03-13 15:40:21 +00:00
|
|
|
*/
|
|
|
|
#include "boxes/peer_list_box.h"
|
|
|
|
|
2017-10-22 12:07:57 +00:00
|
|
|
#include <rpl/range.h>
|
2017-03-13 15:40:21 +00:00
|
|
|
#include "styles/style_boxes.h"
|
|
|
|
#include "styles/style_dialogs.h"
|
2017-09-26 17:57:01 +00:00
|
|
|
#include "styles/style_widgets.h"
|
2017-03-13 15:40:21 +00:00
|
|
|
#include "auth_session.h"
|
|
|
|
#include "mainwidget.h"
|
2017-03-14 14:16:03 +00:00
|
|
|
#include "ui/widgets/multi_select.h"
|
2017-03-14 17:04:28 +00:00
|
|
|
#include "ui/widgets/labels.h"
|
2017-09-26 17:57:01 +00:00
|
|
|
#include "ui/widgets/scroll_area.h"
|
2017-10-22 17:06:57 +00:00
|
|
|
#include "ui/widgets/popup_menu.h"
|
2017-08-14 12:53:49 +00:00
|
|
|
#include "ui/effects/round_checkbox.h"
|
|
|
|
#include "ui/effects/ripple_animation.h"
|
2017-12-05 14:07:01 +00:00
|
|
|
#include "ui/empty_userpic.h"
|
2017-09-13 16:57:44 +00:00
|
|
|
#include "ui/wrap/slide_wrap.h"
|
2017-12-28 13:06:06 +00:00
|
|
|
#include "ui/text_options.h"
|
2017-04-13 08:27:10 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2017-08-11 10:42:52 +00:00
|
|
|
#include "observer_peer.h"
|
2017-08-14 12:53:49 +00:00
|
|
|
#include "storage/file_download.h"
|
2017-12-17 08:13:26 +00:00
|
|
|
#include "data/data_peer_values.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "data/data_chat.h"
|
2019-01-18 12:27:37 +00:00
|
|
|
#include "data/data_session.h"
|
2017-03-17 12:05:50 +00:00
|
|
|
#include "window/themes/window_theme.h"
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2019-01-04 11:09:48 +00:00
|
|
|
auto PaintUserpicCallback(
|
|
|
|
not_null<PeerData*> peer,
|
|
|
|
bool respectSavedMessagesChat)
|
|
|
|
-> Fn<void(Painter &p, int x, int y, int outerWidth, int size)> {
|
|
|
|
if (respectSavedMessagesChat && peer->isSelf()) {
|
|
|
|
return [](Painter &p, int x, int y, int outerWidth, int size) {
|
|
|
|
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
|
|
|
peer->paintUserpicLeft(p, x, y, outerWidth, size);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
PeerListBox::PeerListBox(
|
|
|
|
QWidget*,
|
|
|
|
std::unique_ptr<PeerListController> controller,
|
2018-06-04 15:35:11 +00:00
|
|
|
Fn<void(not_null<PeerListBox*>)> init)
|
2017-06-13 21:08:47 +00:00
|
|
|
: _controller(std::move(controller))
|
|
|
|
, _init(std::move(init)) {
|
|
|
|
Expects(_controller != nullptr);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-08-14 12:48:11 +00:00
|
|
|
void PeerListBox::createMultiSelect() {
|
|
|
|
Expects(_select == nullptr);
|
|
|
|
|
2017-05-30 15:21:05 +00:00
|
|
|
auto entity = object_ptr<Ui::MultiSelect>(this, st::contactsMultiSelect, langFactory(lng_participant_filter));
|
2017-09-13 16:57:44 +00:00
|
|
|
_select.create(this, std::move(entity));
|
2017-12-22 07:05:20 +00:00
|
|
|
_select->heightValue(
|
|
|
|
) | rpl::start_with_next(
|
|
|
|
[this] { updateScrollSkips(); },
|
|
|
|
lifetime());
|
2018-05-31 12:20:28 +00:00
|
|
|
_select->entity()->setSubmittedCallback([this](Qt::KeyboardModifiers) { content()->submitted(); });
|
2017-08-14 12:48:11 +00:00
|
|
|
_select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); });
|
|
|
|
_select->entity()->setItemRemovedCallback([this](uint64 itemId) {
|
2019-01-18 12:27:37 +00:00
|
|
|
if (auto peer = Auth().data().peerLoaded(itemId)) {
|
2017-08-14 12:48:11 +00:00
|
|
|
if (auto row = peerListFindRow(peer->id)) {
|
2017-09-26 17:57:01 +00:00
|
|
|
content()->changeCheckState(row, false, PeerListRow::SetStyle::Animated);
|
2017-08-14 12:48:11 +00:00
|
|
|
update();
|
|
|
|
}
|
|
|
|
_controller->itemDeselectedHook(peer);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
_select->resizeToWidth(st::boxWideWidth);
|
|
|
|
_select->moveToLeft(0, 0);
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int PeerListBox::getTopScrollSkip() const {
|
|
|
|
auto result = 0;
|
|
|
|
if (_select && !_select->isHidden()) {
|
|
|
|
result += _select->height();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListBox::updateScrollSkips() {
|
2017-08-14 12:48:11 +00:00
|
|
|
// 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;
|
|
|
|
}
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
|
|
|
|
2017-03-13 15:40:21 +00:00
|
|
|
void PeerListBox::prepare() {
|
2017-09-26 17:57:01 +00:00
|
|
|
setContent(setInnerWidget(
|
|
|
|
object_ptr<PeerListContent>(
|
|
|
|
this,
|
|
|
|
_controller.get(),
|
|
|
|
st::peerListBox),
|
|
|
|
st::boxLayerScroll));
|
|
|
|
content()->resizeToWidth(st::boxWideWidth);
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
_controller->setDelegate(this);
|
2017-03-13 15:40:21 +00:00
|
|
|
|
|
|
|
setDimensions(st::boxWideWidth, st::boxMaxListHeight);
|
2017-03-14 14:16:03 +00:00
|
|
|
if (_select) {
|
2017-09-30 19:20:40 +00:00
|
|
|
_select->finishAnimating();
|
2017-12-26 12:41:48 +00:00
|
|
|
Ui::SendPendingMoveResizeEvents(_select);
|
2017-08-14 12:48:11 +00:00
|
|
|
_scrollBottomFixed = true;
|
2017-03-14 14:16:03 +00:00
|
|
|
onScrollToY(0);
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-12-22 07:05:20 +00:00
|
|
|
content()->scrollToRequests(
|
|
|
|
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
|
|
|
onScrollToY(request.ymin, request.ymax);
|
|
|
|
}, lifetime());
|
2017-06-13 21:08:47 +00:00
|
|
|
|
|
|
|
if (_init) {
|
|
|
|
_init(this);
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
|
|
|
if (e->key() == Qt::Key_Down) {
|
2017-09-26 17:57:01 +00:00
|
|
|
content()->selectSkip(1);
|
2017-03-13 15:40:21 +00:00
|
|
|
} else if (e->key() == Qt::Key_Up) {
|
2017-09-26 17:57:01 +00:00
|
|
|
content()->selectSkip(-1);
|
2017-03-13 15:40:21 +00:00
|
|
|
} else if (e->key() == Qt::Key_PageDown) {
|
2017-09-26 17:57:01 +00:00
|
|
|
content()->selectSkipPage(height(), 1);
|
2017-03-13 15:40:21 +00:00
|
|
|
} else if (e->key() == Qt::Key_PageUp) {
|
2017-09-26 17:57:01 +00:00
|
|
|
content()->selectSkipPage(height(), -1);
|
2017-03-14 17:04:28 +00:00
|
|
|
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
|
|
|
|
_select->entity()->clearQuery();
|
2017-03-13 15:40:21 +00:00
|
|
|
} else {
|
|
|
|
BoxContent::keyPressEvent(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-14 17:04:28 +00:00
|
|
|
void PeerListBox::searchQueryChanged(const QString &query) {
|
|
|
|
onScrollToY(0);
|
2017-09-26 17:57:01 +00:00
|
|
|
content()->searchQueryChanged(query);
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
|
|
|
|
2017-03-13 15:40:21 +00:00
|
|
|
void PeerListBox::resizeEvent(QResizeEvent *e) {
|
|
|
|
BoxContent::resizeEvent(e);
|
|
|
|
|
2017-03-14 14:16:03 +00:00
|
|
|
if (_select) {
|
|
|
|
_select->resizeToWidth(width());
|
|
|
|
_select->moveToLeft(0, 0);
|
|
|
|
|
|
|
|
updateScrollSkips();
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
content()->resizeToWidth(width());
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-07-03 12:39:23 +00:00
|
|
|
void PeerListBox::paintEvent(QPaintEvent *e) {
|
|
|
|
Painter p(this);
|
|
|
|
for (auto rect : e->region().rects()) {
|
|
|
|
p.fillRect(rect, st::contactsBg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-14 14:16:03 +00:00
|
|
|
void PeerListBox::setInnerFocus() {
|
2017-10-01 08:15:40 +00:00
|
|
|
if (!_select || !_select->toggled()) {
|
2017-09-26 17:57:01 +00:00
|
|
|
content()->setFocus();
|
2017-03-14 14:16:03 +00:00
|
|
|
} else {
|
|
|
|
_select->entity()->setInnerFocus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListBox::peerListSetRowChecked(
|
|
|
|
not_null<PeerListRow*> row,
|
|
|
|
bool checked) {
|
2017-03-17 12:05:50 +00:00
|
|
|
auto peer = row->peer();
|
|
|
|
if (checked) {
|
2017-06-13 21:08:47 +00:00
|
|
|
addSelectItem(peer, PeerListRow::SetStyle::Animated);
|
2017-09-26 17:57:01 +00:00
|
|
|
PeerListContentDelegate::peerListSetRowChecked(row, checked);
|
2017-06-13 21:08:47 +00:00
|
|
|
peerListUpdateRow(row);
|
2017-03-17 16:19:57 +00:00
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
// This call deletes row from _searchRows.
|
2017-03-17 16:19:57 +00:00
|
|
|
_select->entity()->clearQuery();
|
2017-03-17 12:05:50 +00:00
|
|
|
} else {
|
|
|
|
// The itemRemovedCallback will call changeCheckState() here.
|
|
|
|
_select->entity()->removeItem(peer->id);
|
2017-06-13 21:08:47 +00:00
|
|
|
peerListUpdateRow(row);
|
2017-03-17 12:05:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListBox::peerListScrollToTop() {
|
|
|
|
onScrollToY(0);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) {
|
2017-09-26 17:57:01 +00:00
|
|
|
PeerListContentDelegate::peerListSetSearchMode(mode);
|
|
|
|
|
2017-08-14 12:48:11 +00:00
|
|
|
auto selectVisible = (mode != PeerListSearchMode::Disabled);
|
|
|
|
if (selectVisible && !_select) {
|
|
|
|
createMultiSelect();
|
2017-10-01 08:15:40 +00:00
|
|
|
_select->toggle(!selectVisible, anim::type::instant);
|
2017-03-15 15:10:18 +00:00
|
|
|
}
|
|
|
|
if (_select) {
|
2017-10-01 08:15:40 +00:00
|
|
|
_select->toggle(selectVisible, anim::type::normal);
|
2017-08-14 12:48:11 +00:00
|
|
|
_scrollBottomFixed = false;
|
|
|
|
setInnerFocus();
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
PeerListController::PeerListController(std::unique_ptr<PeerListSearchController> searchController) : _searchController(std::move(searchController)) {
|
|
|
|
if (_searchController) {
|
|
|
|
_searchController->setDelegate(this);
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-19 08:18:20 +00:00
|
|
|
bool PeerListController::hasComplexSearch() const {
|
|
|
|
return (_searchController != nullptr);
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListController::search(const QString &query) {
|
2017-07-19 08:18:20 +00:00
|
|
|
Expects(hasComplexSearch());
|
2017-06-13 21:08:47 +00:00
|
|
|
_searchController->searchQuery(query);
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
void PeerListController::peerListSearchAddRow(not_null<PeerData*> peer) {
|
2017-06-13 21:08:47 +00:00
|
|
|
if (auto row = delegate()->peerListFindRow(peer->id)) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(row->id() == row->peer()->id);
|
2017-06-13 21:08:47 +00:00
|
|
|
delegate()->peerListAppendFoundRow(row);
|
|
|
|
} else if (auto row = createSearchRow(peer)) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(row->id() == row->peer()->id);
|
2017-06-13 21:08:47 +00:00
|
|
|
delegate()->peerListAppendSearchRow(std::move(row));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListController::peerListSearchRefreshRows() {
|
|
|
|
delegate()->peerListRefreshRows();
|
|
|
|
}
|
|
|
|
|
2017-10-22 12:07:57 +00:00
|
|
|
rpl::producer<int> PeerListController::onlineCountValue() const {
|
|
|
|
return rpl::single(0);
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListController::setDescriptionText(const QString &text) {
|
|
|
|
if (text.isEmpty()) {
|
|
|
|
setDescription(nullptr);
|
2017-03-14 18:38:50 +00:00
|
|
|
} else {
|
2017-06-13 21:08:47 +00:00
|
|
|
setDescription(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout));
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListController::setSearchLoadingText(const QString &text) {
|
|
|
|
if (text.isEmpty()) {
|
|
|
|
setSearchLoading(nullptr);
|
|
|
|
} else {
|
|
|
|
setSearchLoading(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout));
|
|
|
|
}
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListController::setSearchNoResultsText(const QString &text) {
|
|
|
|
if (text.isEmpty()) {
|
|
|
|
setSearchNoResults(nullptr);
|
|
|
|
} else {
|
|
|
|
setSearchNoResults(object_ptr<Ui::FlatLabel>(nullptr, text, Ui::FlatLabel::InitType::Simple, st::membersAbout));
|
2017-03-17 12:05:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 11:10:40 +00:00
|
|
|
base::unique_qptr<Ui::PopupMenu> PeerListController::rowContextMenu(
|
2018-07-19 14:27:09 +00:00
|
|
|
QWidget *parent,
|
2018-02-12 11:10:40 +00:00
|
|
|
not_null<PeerListRow*> row) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-12-04 12:08:43 +00:00
|
|
|
std::unique_ptr<PeerListState> PeerListController::saveState() const {
|
2017-10-25 08:38:55 +00:00
|
|
|
return delegate()->peerListSaveState();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListController::restoreState(
|
|
|
|
std::unique_ptr<PeerListState> state) {
|
|
|
|
delegate()->peerListRestoreState(std::move(state));
|
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
void PeerListBox::addSelectItem(not_null<PeerData*> peer, PeerListRow::SetStyle style) {
|
2017-08-14 12:48:11 +00:00
|
|
|
if (!_select) {
|
|
|
|
createMultiSelect();
|
2017-10-01 08:15:40 +00:00
|
|
|
_select->hide(anim::type::instant);
|
2017-08-14 12:48:11 +00:00
|
|
|
}
|
2017-12-05 14:07:01 +00:00
|
|
|
const auto respect = _controller->respectSavedMessagesChat();
|
|
|
|
const auto text = (respect && peer->isSelf())
|
|
|
|
? lang(lng_saved_short)
|
|
|
|
: peer->shortName();
|
|
|
|
const auto callback = PaintUserpicCallback(peer, respect);
|
2017-06-13 21:08:47 +00:00
|
|
|
if (style == PeerListRow::SetStyle::Fast) {
|
2017-12-05 14:07:01 +00:00
|
|
|
_select->entity()->addItemInBunch(
|
|
|
|
peer->id,
|
|
|
|
text,
|
|
|
|
st::activeButtonBg,
|
|
|
|
std::move(callback));
|
2017-03-17 12:05:50 +00:00
|
|
|
} else {
|
2017-12-05 14:07:01 +00:00
|
|
|
_select->entity()->addItem(
|
|
|
|
peer->id,
|
|
|
|
text,
|
|
|
|
st::activeButtonBg,
|
|
|
|
std::move(callback));
|
2017-03-17 12:05:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListBox::peerListFinishSelectedRowsBunch() {
|
2017-03-17 12:05:50 +00:00
|
|
|
Expects(_select != nullptr);
|
|
|
|
_select->entity()->finishItemsBunch();
|
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
bool PeerListBox::peerListIsRowSelected(not_null<PeerData*> peer) {
|
2017-08-14 12:48:11 +00:00
|
|
|
return _select ? _select->entity()->hasItem(peer->id) : false;
|
2017-03-17 12:05:50 +00:00
|
|
|
}
|
|
|
|
|
2017-08-14 12:53:49 +00:00
|
|
|
int PeerListBox::peerListSelectedRowsCount() {
|
2017-09-04 12:20:10 +00:00
|
|
|
return _select ? _select->entity()->getItemsCount() : 0;
|
2017-08-14 12:53:49 +00:00
|
|
|
}
|
|
|
|
|
2019-01-13 13:28:05 +00:00
|
|
|
auto PeerListBox::peerListCollectSelectedRows()
|
|
|
|
-> std::vector<not_null<PeerData*>> {
|
|
|
|
auto result = std::vector<not_null<PeerData*>>();
|
|
|
|
auto items = _select
|
|
|
|
? _select->entity()->getItems()
|
|
|
|
: QVector<uint64>();
|
2017-06-13 21:08:47 +00:00
|
|
|
if (!items.empty()) {
|
|
|
|
result.reserve(items.size());
|
2019-01-13 13:28:05 +00:00
|
|
|
for (const auto itemId : items) {
|
2019-01-18 12:27:37 +00:00
|
|
|
result.push_back(Auth().data().peer(itemId));
|
2017-06-13 21:08:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
PeerListRow::PeerListRow(not_null<PeerData*> peer) : PeerListRow(peer, peer->id) {
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
PeerListRow::PeerListRow(not_null<PeerData*> peer, PeerListRowId id)
|
2017-06-16 15:26:19 +00:00
|
|
|
: _id(id)
|
|
|
|
, _peer(peer)
|
|
|
|
, _initialized(false)
|
2017-12-05 14:07:01 +00:00
|
|
|
, _isSearchResult(false)
|
|
|
|
, _isSavedMessagesChat(false) {
|
2017-06-13 21:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool PeerListRow::checked() const {
|
2017-04-27 19:04:45 +00:00
|
|
|
return _checkbox && _checkbox->checked();
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListRow::setCustomStatus(const QString &status) {
|
2017-06-28 05:57:58 +00:00
|
|
|
setStatusText(status);
|
2017-03-13 15:40:21 +00:00
|
|
|
_statusType = StatusType::Custom;
|
2017-10-22 12:07:57 +00:00
|
|
|
_statusValidTill = 0;
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListRow::clearCustomStatus() {
|
2017-03-13 15:40:21 +00:00
|
|
|
_statusType = StatusType::Online;
|
|
|
|
refreshStatus();
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListRow::refreshStatus() {
|
2017-03-13 15:40:21 +00:00
|
|
|
if (!_initialized || _statusType == StatusType::Custom) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-15 09:35:19 +00:00
|
|
|
_statusType = StatusType::LastSeen;
|
2017-10-22 12:07:57 +00:00
|
|
|
_statusValidTill = 0;
|
2017-03-13 15:40:21 +00:00
|
|
|
if (auto user = peer()->asUser()) {
|
2017-12-05 14:07:01 +00:00
|
|
|
if (_isSavedMessagesChat) {
|
|
|
|
setStatusText(lang(lng_saved_forward_here));
|
|
|
|
} else {
|
|
|
|
auto time = unixtime();
|
2017-12-17 08:13:26 +00:00
|
|
|
setStatusText(Data::OnlineText(user, time));
|
|
|
|
if (Data::OnlineTextActive(user, time)) {
|
2017-12-05 14:07:01 +00:00
|
|
|
_statusType = StatusType::Online;
|
|
|
|
}
|
|
|
|
_statusValidTill = getms()
|
2017-12-17 08:13:26 +00:00
|
|
|
+ Data::OnlineChangeTimeout(user, time);
|
2017-08-15 09:35:19 +00:00
|
|
|
}
|
|
|
|
} else if (auto chat = peer()->asChat()) {
|
|
|
|
if (!chat->amIn()) {
|
|
|
|
setStatusText(lang(lng_chat_status_unaccessible));
|
|
|
|
} else if (chat->count > 0) {
|
|
|
|
setStatusText(lng_chat_status_members(lt_count, chat->count));
|
|
|
|
} else {
|
|
|
|
setStatusText(lang(lng_group_status));
|
|
|
|
}
|
|
|
|
} else if (peer()->isMegagroup()) {
|
|
|
|
setStatusText(lang(lng_group_status));
|
|
|
|
} else if (peer()->isChannel()) {
|
|
|
|
setStatusText(lang(lng_channel_status));
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-22 12:07:57 +00:00
|
|
|
TimeMs PeerListRow::refreshStatusTime() const {
|
|
|
|
return _statusValidTill;
|
|
|
|
}
|
|
|
|
|
2017-10-10 20:39:44 +00:00
|
|
|
void PeerListRow::refreshName(const style::PeerListItem &st) {
|
2017-03-13 15:40:21 +00:00
|
|
|
if (!_initialized) {
|
|
|
|
return;
|
|
|
|
}
|
2017-12-05 14:07:01 +00:00
|
|
|
const auto text = _isSavedMessagesChat
|
|
|
|
? lang(lng_saved_messages)
|
|
|
|
: peer()->name;
|
2017-12-28 13:06:06 +00:00
|
|
|
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
PeerListRow::~PeerListRow() = default;
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListRow::invalidatePixmapsCache() {
|
2017-03-17 12:05:50 +00:00
|
|
|
if (_checkbox) {
|
|
|
|
_checkbox->invalidateCache();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-07 17:53:00 +00:00
|
|
|
int PeerListRow::nameIconWidth() const {
|
|
|
|
return _peer->isVerified() ? st::dialogsVerifiedIcon.width() : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListRow::paintNameIcon(
|
|
|
|
Painter &p,
|
|
|
|
int x,
|
|
|
|
int y,
|
2017-11-08 16:45:30 +00:00
|
|
|
int outerWidth,
|
|
|
|
bool selected) {
|
2017-11-07 17:53:00 +00:00
|
|
|
st::dialogsVerifiedIcon.paint(p, x, y, outerWidth);
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListRow::paintStatusText(
|
|
|
|
Painter &p,
|
|
|
|
const style::PeerListItem &st,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int availableWidth,
|
|
|
|
int outerWidth,
|
|
|
|
bool selected) {
|
2017-06-13 21:08:47 +00:00
|
|
|
auto statusHasOnlineColor = (_statusType == PeerListRow::StatusType::Online);
|
2017-06-28 05:57:58 +00:00
|
|
|
p.setFont(st::contactsStatusFont);
|
2017-09-26 17:57:01 +00:00
|
|
|
p.setPen(statusHasOnlineColor ? st.statusFgActive : (selected ? st.statusFgOver : st.statusFg));
|
2017-06-30 11:36:13 +00:00
|
|
|
_status.drawLeftElided(p, x, y, availableWidth, outerWidth);
|
2017-04-27 19:04:45 +00:00
|
|
|
}
|
|
|
|
|
2017-03-13 15:40:21 +00:00
|
|
|
template <typename UpdateCallback>
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListRow::addRipple(const style::PeerListItem &st, QSize size, QPoint point, UpdateCallback updateCallback) {
|
2017-03-13 15:40:21 +00:00
|
|
|
if (!_ripple) {
|
|
|
|
auto mask = Ui::RippleAnimation::rectMask(size);
|
2017-09-26 17:57:01 +00:00
|
|
|
_ripple = std::make_unique<Ui::RippleAnimation>(st.button.ripple, std::move(mask), std::move(updateCallback));
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
_ripple->add(point);
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListRow::stopLastRipple() {
|
2017-03-13 15:40:21 +00:00
|
|
|
if (_ripple) {
|
|
|
|
_ripple->lastStop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListRow::paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth) {
|
2017-03-13 15:40:21 +00:00
|
|
|
if (_ripple) {
|
|
|
|
_ripple->paint(p, x, y, outerWidth, ms);
|
|
|
|
if (_ripple->empty()) {
|
|
|
|
_ripple.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListRow::paintUserpic(
|
|
|
|
Painter &p,
|
|
|
|
const style::PeerListItem &st,
|
|
|
|
TimeMs ms,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int outerWidth) {
|
2017-08-14 12:53:49 +00:00
|
|
|
if (_disabledState == State::DisabledChecked) {
|
2017-09-26 17:57:01 +00:00
|
|
|
paintDisabledCheckUserpic(p, st, x, y, outerWidth);
|
2017-08-14 12:53:49 +00:00
|
|
|
} else if (_checkbox) {
|
|
|
|
_checkbox->paint(p, ms, x, y, outerWidth);
|
2017-12-05 14:07:01 +00:00
|
|
|
} else if (_isSavedMessagesChat) {
|
|
|
|
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, st.photoSize);
|
2017-03-17 12:05:50 +00:00
|
|
|
} else {
|
2017-09-26 17:57:01 +00:00
|
|
|
peer()->paintUserpicLeft(p, x, y, outerWidth, st.photoSize);
|
2017-03-17 12:05:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Emulates Ui::RoundImageCheckbox::paint() in a checked state.
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListRow::paintDisabledCheckUserpic(
|
|
|
|
Painter &p,
|
|
|
|
const style::PeerListItem &st,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int outerWidth) const {
|
2017-03-17 12:05:50 +00:00
|
|
|
auto userpicRadius = st::contactsPhotoCheckbox.imageSmallRadius;
|
|
|
|
auto userpicShift = st::contactsPhotoCheckbox.imageRadius - userpicRadius;
|
|
|
|
auto userpicDiameter = st::contactsPhotoCheckbox.imageRadius * 2;
|
|
|
|
auto userpicLeft = x + userpicShift;
|
|
|
|
auto userpicTop = y + userpicShift;
|
|
|
|
auto userpicEllipse = rtlrect(x, y, userpicDiameter, userpicDiameter, outerWidth);
|
|
|
|
auto userpicBorderPen = st::contactsPhotoDisabledCheckFg->p;
|
|
|
|
userpicBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth);
|
|
|
|
|
|
|
|
auto iconDiameter = st::contactsPhotoCheckbox.check.size;
|
|
|
|
auto iconLeft = x + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter;
|
|
|
|
auto iconTop = y + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter;
|
|
|
|
auto iconEllipse = rtlrect(iconLeft, iconTop, iconDiameter, iconDiameter, outerWidth);
|
|
|
|
auto iconBorderPen = st::contactsPhotoCheckbox.check.border->p;
|
|
|
|
iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth);
|
|
|
|
|
2017-12-05 14:07:01 +00:00
|
|
|
if (_isSavedMessagesChat) {
|
|
|
|
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
|
|
|
} else {
|
|
|
|
peer()->paintUserpicLeft(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
|
|
|
}
|
2017-03-17 12:05:50 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
PainterHighQualityEnabler hq(p);
|
|
|
|
|
|
|
|
p.setPen(userpicBorderPen);
|
|
|
|
p.setBrush(Qt::NoBrush);
|
|
|
|
p.drawEllipse(userpicEllipse);
|
|
|
|
|
|
|
|
p.setPen(iconBorderPen);
|
|
|
|
p.setBrush(st::contactsPhotoDisabledCheckFg);
|
|
|
|
p.drawEllipse(iconEllipse);
|
|
|
|
}
|
|
|
|
|
|
|
|
st::contactsPhotoCheckbox.check.check.paint(p, iconEllipse.topLeft(), outerWidth);
|
|
|
|
}
|
|
|
|
|
2017-06-28 05:57:58 +00:00
|
|
|
void PeerListRow::setStatusText(const QString &text) {
|
2017-12-28 13:06:06 +00:00
|
|
|
_status.setText(st::defaultTextStyle, text, Ui::NameTextOptions());
|
2017-06-28 05:57:58 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
float64 PeerListRow::checkedRatio() {
|
2017-03-17 12:05:50 +00:00
|
|
|
return _checkbox ? _checkbox->checkedAnimationRatio() : 0.;
|
|
|
|
}
|
|
|
|
|
2017-10-10 20:39:44 +00:00
|
|
|
void PeerListRow::lazyInitialize(const style::PeerListItem &st) {
|
2017-03-13 15:40:21 +00:00
|
|
|
if (_initialized) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_initialized = true;
|
2017-10-10 20:39:44 +00:00
|
|
|
refreshName(st);
|
2017-03-13 15:40:21 +00:00
|
|
|
refreshStatus();
|
|
|
|
}
|
|
|
|
|
2018-06-04 15:35:11 +00:00
|
|
|
void PeerListRow::createCheckbox(Fn<void()> updateCallback) {
|
2017-09-26 17:57:01 +00:00
|
|
|
_checkbox = std::make_unique<Ui::RoundImageCheckbox>(
|
|
|
|
st::contactsPhotoCheckbox,
|
|
|
|
std::move(updateCallback),
|
2017-12-05 14:07:01 +00:00
|
|
|
PaintUserpicCallback(_peer, _isSavedMessagesChat));
|
2017-03-17 12:05:50 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
void PeerListRow::setCheckedInternal(bool checked, SetStyle style) {
|
2017-03-17 12:05:50 +00:00
|
|
|
Expects(_checkbox != nullptr);
|
|
|
|
using CheckboxStyle = Ui::RoundCheckbox::SetStyle;
|
|
|
|
auto speed = (style == SetStyle::Animated) ? CheckboxStyle::Animated : CheckboxStyle::Fast;
|
|
|
|
_checkbox->setChecked(checked, speed);
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
PeerListContent::PeerListContent(
|
|
|
|
QWidget *parent,
|
|
|
|
not_null<PeerListController*> controller,
|
|
|
|
const style::PeerList &st)
|
|
|
|
: RpWidget(parent)
|
|
|
|
, _st(st)
|
2017-03-13 15:40:21 +00:00
|
|
|
, _controller(controller)
|
2017-09-26 17:57:01 +00:00
|
|
|
, _rowHeight(_st.item.height) {
|
2017-08-04 14:54:32 +00:00
|
|
|
subscribe(Auth().downloaderTaskFinished(), [this] { update(); });
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-08-11 10:42:52 +00:00
|
|
|
using UpdateFlag = Notify::PeerUpdate::Flag;
|
|
|
|
auto changes = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged;
|
|
|
|
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) {
|
|
|
|
if (update.flags & UpdateFlag::PhotoChanged) {
|
|
|
|
this->update();
|
|
|
|
} else if (update.flags & UpdateFlag::NameChanged) {
|
|
|
|
handleNameChanged(update);
|
|
|
|
}
|
|
|
|
}));
|
2017-03-17 12:05:50 +00:00
|
|
|
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) {
|
|
|
|
if (update.paletteChanged()) {
|
|
|
|
invalidatePixmapsCache();
|
|
|
|
}
|
|
|
|
});
|
2017-10-22 12:07:57 +00:00
|
|
|
_repaintByStatus.setCallback([this] { update(); });
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::appendRow(std::unique_ptr<PeerListRow> row) {
|
2017-06-13 21:08:47 +00:00
|
|
|
Expects(row != nullptr);
|
2018-03-02 17:17:33 +00:00
|
|
|
|
2017-04-27 19:04:45 +00:00
|
|
|
if (_rowsById.find(row->id()) == _rowsById.cend()) {
|
2017-03-14 17:04:28 +00:00
|
|
|
row->setAbsoluteIndex(_rows.size());
|
2017-03-14 14:16:03 +00:00
|
|
|
addRowEntry(row.get());
|
2017-03-13 15:40:21 +00:00
|
|
|
_rows.push_back(std::move(row));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::appendSearchRow(std::unique_ptr<PeerListRow> row) {
|
2017-06-13 21:08:47 +00:00
|
|
|
Expects(row != nullptr);
|
2017-03-17 12:05:50 +00:00
|
|
|
Expects(showingSearch());
|
2018-03-02 17:17:33 +00:00
|
|
|
|
2017-04-27 19:04:45 +00:00
|
|
|
if (_rowsById.find(row->id()) == _rowsById.cend()) {
|
2017-06-13 21:08:47 +00:00
|
|
|
row->setAbsoluteIndex(_searchRows.size());
|
|
|
|
row->setIsSearchResult(true);
|
2017-03-14 18:38:50 +00:00
|
|
|
addRowEntry(row.get());
|
|
|
|
_filterResults.push_back(row.get());
|
2017-06-13 21:08:47 +00:00
|
|
|
_searchRows.push_back(std::move(row));
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::appendFoundRow(not_null<PeerListRow*> row) {
|
2017-06-13 21:08:47 +00:00
|
|
|
Expects(showingSearch());
|
2018-03-02 17:17:33 +00:00
|
|
|
|
2017-06-13 21:08:47 +00:00
|
|
|
auto index = findRowIndex(row);
|
|
|
|
if (index.value < 0) {
|
|
|
|
_filterResults.push_back(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-02 17:17:33 +00:00
|
|
|
void PeerListContent::changeCheckState(
|
|
|
|
not_null<PeerListRow*> row,
|
|
|
|
bool checked,
|
|
|
|
PeerListRow::SetStyle style) {
|
2017-12-05 14:07:01 +00:00
|
|
|
row->setChecked(
|
|
|
|
checked,
|
|
|
|
style,
|
|
|
|
[this, row] { updateRow(row); });
|
2017-03-17 12:05:50 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
2017-12-05 14:07:01 +00:00
|
|
|
if (_controller->respectSavedMessagesChat() && row->peer()->isSelf()) {
|
|
|
|
row->setIsSavedMessagesChat(true);
|
|
|
|
}
|
2017-04-27 19:04:45 +00:00
|
|
|
_rowsById.emplace(row->id(), row);
|
|
|
|
_rowsByPeer[row->peer()].push_back(row);
|
2017-03-14 18:38:50 +00:00
|
|
|
if (addingToSearchIndex()) {
|
2017-03-14 14:16:03 +00:00
|
|
|
addToSearchIndex(row);
|
|
|
|
}
|
2017-08-14 12:48:11 +00:00
|
|
|
if (_controller->isRowSelected(row->peer())) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(row->id() == row->peer()->id);
|
2017-08-14 12:48:11 +00:00
|
|
|
changeCheckState(row, true, PeerListRow::SetStyle::Fast);
|
2017-03-17 12:05:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::invalidatePixmapsCache() {
|
2017-08-14 12:48:11 +00:00
|
|
|
auto invalidate = [](auto &&row) { row->invalidatePixmapsCache(); };
|
2017-11-20 12:23:20 +00:00
|
|
|
ranges::for_each(_rows, invalidate);
|
|
|
|
ranges::for_each(_searchRows, invalidate);
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
bool PeerListContent::addingToSearchIndex() const {
|
2017-03-14 18:38:50 +00:00
|
|
|
// If we started indexing already, we continue.
|
2017-07-19 08:18:20 +00:00
|
|
|
return (_searchMode != PeerListSearchMode::Disabled) || !_searchIndex.empty();
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::addToSearchIndex(not_null<PeerListRow*> row) {
|
2017-06-13 21:08:47 +00:00
|
|
|
if (row->isSearchResult()) {
|
2017-03-14 18:38:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-14 14:16:03 +00:00
|
|
|
removeFromSearchIndex(row);
|
2018-01-22 09:33:09 +00:00
|
|
|
row->setNameFirstLetters(row->peer()->nameFirstLetters());
|
|
|
|
for (auto ch : row->nameFirstLetters()) {
|
2017-03-14 14:16:03 +00:00
|
|
|
_searchIndex[ch].push_back(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::removeFromSearchIndex(not_null<PeerListRow*> row) {
|
2018-01-22 09:33:09 +00:00
|
|
|
const auto &nameFirstLetters = row->nameFirstLetters();
|
|
|
|
if (!nameFirstLetters.empty()) {
|
|
|
|
for (auto ch : row->nameFirstLetters()) {
|
2017-03-14 14:16:03 +00:00
|
|
|
auto it = _searchIndex.find(ch);
|
|
|
|
if (it != _searchIndex.cend()) {
|
|
|
|
auto &entry = it->second;
|
2018-10-25 09:44:33 +00:00
|
|
|
entry.erase(ranges::remove(entry, row), end(entry));
|
2017-03-14 14:16:03 +00:00
|
|
|
if (entry.empty()) {
|
|
|
|
_searchIndex.erase(it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-22 09:33:09 +00:00
|
|
|
row->setNameFirstLetters({});
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::prependRow(std::unique_ptr<PeerListRow> row) {
|
2017-06-13 21:08:47 +00:00
|
|
|
Expects(row != nullptr);
|
2018-10-25 09:44:33 +00:00
|
|
|
|
2017-04-27 19:04:45 +00:00
|
|
|
if (_rowsById.find(row->id()) == _rowsById.cend()) {
|
2017-03-14 17:04:28 +00:00
|
|
|
addRowEntry(row.get());
|
2017-03-13 15:40:21 +00:00
|
|
|
_rows.insert(_rows.begin(), std::move(row));
|
2017-03-14 14:16:03 +00:00
|
|
|
refreshIndices();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::prependRowFromSearchResult(not_null<PeerListRow*> row) {
|
2017-07-19 08:18:20 +00:00
|
|
|
if (!row->isSearchResult()) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(_rowsById.find(row->id()) != _rowsById.cend());
|
2017-07-19 08:18:20 +00:00
|
|
|
auto index = row->absoluteIndex();
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(index >= 0 && index < _searchRows.size());
|
|
|
|
Assert(_searchRows[index].get() == row);
|
2017-07-19 08:18:20 +00:00
|
|
|
|
|
|
|
row->setIsSearchResult(false);
|
|
|
|
_rows.insert(_rows.begin(), std::move(_searchRows[index]));
|
|
|
|
refreshIndices();
|
|
|
|
removeRowAtIndex(_searchRows, index);
|
|
|
|
|
|
|
|
if (addingToSearchIndex()) {
|
|
|
|
addToSearchIndex(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::refreshIndices() {
|
2017-03-14 14:16:03 +00:00
|
|
|
auto index = 0;
|
|
|
|
for (auto &row : _rows) {
|
2017-03-14 17:04:28 +00:00
|
|
|
row->setAbsoluteIndex(index++);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-24 17:11:35 +00:00
|
|
|
void PeerListContent::removeRowAtIndex(
|
|
|
|
std::vector<std::unique_ptr<PeerListRow>> &from,
|
|
|
|
int index) {
|
2017-07-19 08:18:20 +00:00
|
|
|
from.erase(from.begin() + index);
|
|
|
|
for (auto i = index, count = int(from.size()); i != count; ++i) {
|
|
|
|
from[i]->setAbsoluteIndex(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
PeerListRow *PeerListContent::findRow(PeerListRowId id) {
|
2017-04-27 19:04:45 +00:00
|
|
|
auto it = _rowsById.find(id);
|
2017-06-13 21:08:47 +00:00
|
|
|
return (it == _rowsById.cend()) ? nullptr : it->second.get();
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::removeRow(not_null<PeerListRow*> row) {
|
2017-03-14 17:04:28 +00:00
|
|
|
auto index = row->absoluteIndex();
|
2017-06-16 15:26:19 +00:00
|
|
|
auto isSearchResult = row->isSearchResult();
|
|
|
|
auto &eraseFrom = isSearchResult ? _searchRows : _rows;
|
2017-03-14 18:38:50 +00:00
|
|
|
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(index >= 0 && index < eraseFrom.size());
|
|
|
|
Assert(eraseFrom[index].get() == row);
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-10-24 17:11:35 +00:00
|
|
|
auto pressedData = saveSelectedData(_pressed);
|
|
|
|
auto contextedData = saveSelectedData(_contexted);
|
2017-03-14 17:04:28 +00:00
|
|
|
setSelected(Selected());
|
|
|
|
setPressed(Selected());
|
2017-10-24 17:11:35 +00:00
|
|
|
setContexted(Selected());
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-04-27 19:04:45 +00:00
|
|
|
_rowsById.erase(row->id());
|
|
|
|
auto &byPeer = _rowsByPeer[row->peer()];
|
2018-10-25 09:44:33 +00:00
|
|
|
byPeer.erase(ranges::remove(byPeer, row), end(byPeer));
|
2017-03-14 18:38:50 +00:00
|
|
|
removeFromSearchIndex(row);
|
2017-10-24 17:11:35 +00:00
|
|
|
_filterResults.erase(
|
2018-10-25 09:44:33 +00:00
|
|
|
ranges::remove(_filterResults, row),
|
|
|
|
end(_filterResults));
|
2017-07-19 08:18:20 +00:00
|
|
|
removeRowAtIndex(eraseFrom, index);
|
2017-03-14 17:04:28 +00:00
|
|
|
|
|
|
|
restoreSelection();
|
2017-10-24 17:11:35 +00:00
|
|
|
setPressed(restoreSelectedData(pressedData));
|
|
|
|
setContexted(restoreSelectedData(contextedData));
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListContent::clearAllContent() {
|
|
|
|
setSelected(Selected());
|
|
|
|
setPressed(Selected());
|
|
|
|
setContexted(Selected());
|
2018-12-25 12:41:40 +00:00
|
|
|
_mouseSelection = false;
|
|
|
|
_lastMousePosition = std::nullopt;
|
2017-10-24 17:11:35 +00:00
|
|
|
_rowsById.clear();
|
|
|
|
_rowsByPeer.clear();
|
|
|
|
_filterResults.clear();
|
|
|
|
_searchIndex.clear();
|
|
|
|
_rows.clear();
|
|
|
|
_searchRows.clear();
|
|
|
|
_searchQuery
|
|
|
|
= _normalizedSearchQuery
|
|
|
|
= _mentionHighlight
|
|
|
|
= QString();
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::convertRowToSearchResult(not_null<PeerListRow*> row) {
|
2017-07-19 08:18:20 +00:00
|
|
|
if (row->isSearchResult()) {
|
|
|
|
return;
|
|
|
|
} else if (!showingSearch() || !_controller->hasComplexSearch()) {
|
|
|
|
return removeRow(row);
|
|
|
|
}
|
|
|
|
auto index = row->absoluteIndex();
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(index >= 0 && index < _rows.size());
|
|
|
|
Assert(_rows[index].get() == row);
|
2017-07-19 08:18:20 +00:00
|
|
|
|
|
|
|
removeFromSearchIndex(row);
|
|
|
|
row->setIsSearchResult(true);
|
|
|
|
row->setAbsoluteIndex(_searchRows.size());
|
|
|
|
_searchRows.push_back(std::move(_rows[index]));
|
|
|
|
removeRowAtIndex(_rows, index);
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
int PeerListContent::fullRowsCount() const {
|
2017-03-14 14:16:03 +00:00
|
|
|
return _rows.size();
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
not_null<PeerListRow*> PeerListContent::rowAt(int index) const {
|
2017-04-27 19:04:45 +00:00
|
|
|
Expects(index >= 0 && index < _rows.size());
|
|
|
|
return _rows[index].get();
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::setDescription(object_ptr<Ui::FlatLabel> description) {
|
2017-06-13 21:08:47 +00:00
|
|
|
_description = std::move(description);
|
|
|
|
if (_description) {
|
|
|
|
_description->setParent(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::setSearchLoading(object_ptr<Ui::FlatLabel> loading) {
|
2017-06-13 21:08:47 +00:00
|
|
|
_searchLoading = std::move(loading);
|
|
|
|
if (_searchLoading) {
|
|
|
|
_searchLoading->setParent(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::setSearchNoResults(object_ptr<Ui::FlatLabel> noResults) {
|
2017-06-13 21:08:47 +00:00
|
|
|
_searchNoResults = std::move(noResults);
|
|
|
|
if (_searchNoResults) {
|
|
|
|
_searchNoResults->setParent(this);
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::setAboveWidget(object_ptr<TWidget> aboveWidget) {
|
2017-08-14 12:48:11 +00:00
|
|
|
_aboveWidget = std::move(aboveWidget);
|
|
|
|
if (_aboveWidget) {
|
|
|
|
_aboveWidget->setParent(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
int PeerListContent::labelHeight() const {
|
2017-03-14 18:38:50 +00:00
|
|
|
auto computeLabelHeight = [](auto &label) {
|
|
|
|
if (!label) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return st::membersAboutLimitPadding.top() + label->height() + st::membersAboutLimitPadding.bottom();
|
|
|
|
};
|
2017-03-14 17:04:28 +00:00
|
|
|
if (showingSearch()) {
|
2017-03-14 18:38:50 +00:00
|
|
|
if (!_filterResults.empty()) {
|
|
|
|
return 0;
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
2017-06-13 21:08:47 +00:00
|
|
|
if (_controller->isSearchLoading()) {
|
2017-03-14 18:38:50 +00:00
|
|
|
return computeLabelHeight(_searchLoading);
|
|
|
|
}
|
|
|
|
return computeLabelHeight(_searchNoResults);
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
2017-06-13 21:08:47 +00:00
|
|
|
return computeLabelHeight(_description);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::refreshRows() {
|
|
|
|
resizeToWidth(width());
|
2017-03-13 15:40:21 +00:00
|
|
|
if (_visibleBottom > 0) {
|
|
|
|
checkScrollForPreload();
|
|
|
|
}
|
2018-12-25 12:41:40 +00:00
|
|
|
if (_mouseSelection) {
|
|
|
|
selectByMouse(QCursor::pos());
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::setSearchMode(PeerListSearchMode mode) {
|
2017-03-14 18:38:50 +00:00
|
|
|
if (_searchMode != mode) {
|
|
|
|
if (!addingToSearchIndex()) {
|
|
|
|
for_const (auto &row, _rows) {
|
|
|
|
addToSearchIndex(row.get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_searchMode = mode;
|
2017-07-19 08:18:20 +00:00
|
|
|
if (_controller->hasComplexSearch()) {
|
2017-03-14 18:38:50 +00:00
|
|
|
if (!_searchLoading) {
|
2017-09-26 17:57:01 +00:00
|
|
|
setSearchLoading(object_ptr<Ui::FlatLabel>(
|
|
|
|
this,
|
|
|
|
lang(lng_contacts_loading),
|
|
|
|
Ui::FlatLabel::InitType::Simple,
|
|
|
|
st::membersAbout));
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
2017-06-13 21:08:47 +00:00
|
|
|
clearSearchRows();
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::clearSearchRows() {
|
2017-06-13 21:08:47 +00:00
|
|
|
while (!_searchRows.empty()) {
|
|
|
|
removeRow(_searchRows.back().get());
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::paintEvent(QPaintEvent *e) {
|
2017-03-13 15:40:21 +00:00
|
|
|
Painter p(this);
|
|
|
|
|
2017-10-22 12:07:57 +00:00
|
|
|
auto clip = e->rect();
|
|
|
|
p.fillRect(clip, _st.item.button.textBg);
|
|
|
|
|
|
|
|
auto repaintByStatusAfter = _repaintByStatus.remainingTime();
|
|
|
|
auto repaintAfterMin = repaintByStatusAfter;
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-08-14 12:48:11 +00:00
|
|
|
auto rowsTopCached = rowsTop();
|
2017-03-13 15:40:21 +00:00
|
|
|
auto ms = getms();
|
2017-10-22 12:07:57 +00:00
|
|
|
auto yFrom = clip.y() - rowsTopCached;
|
|
|
|
auto yTo = clip.y() + clip.height() - rowsTopCached;
|
2017-08-14 12:48:11 +00:00
|
|
|
p.translate(0, rowsTopCached);
|
2017-03-14 17:04:28 +00:00
|
|
|
auto count = shownRowsCount();
|
|
|
|
if (count > 0) {
|
|
|
|
auto from = floorclamp(yFrom, _rowHeight, 0, count);
|
|
|
|
auto to = ceilclamp(yTo, _rowHeight, 0, count);
|
2017-03-13 15:40:21 +00:00
|
|
|
p.translate(0, from * _rowHeight);
|
|
|
|
for (auto index = from; index != to; ++index) {
|
2017-10-22 12:07:57 +00:00
|
|
|
auto repaintAfter = paintRow(p, ms, RowIndex(index));
|
|
|
|
if (repaintAfter >= 0
|
|
|
|
&& (repaintAfterMin < 0
|
|
|
|
|| repaintAfterMin > repaintAfter)) {
|
|
|
|
repaintAfterMin = repaintAfter;
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
p.translate(0, _rowHeight);
|
|
|
|
}
|
|
|
|
}
|
2017-10-22 12:07:57 +00:00
|
|
|
if (repaintAfterMin != repaintByStatusAfter) {
|
|
|
|
Assert(repaintAfterMin >= 0);
|
|
|
|
_repaintByStatus.callOnce(repaintAfterMin);
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
int PeerListContent::resizeGetHeight(int newWidth) {
|
2017-08-14 12:48:11 +00:00
|
|
|
_aboveHeight = 0;
|
|
|
|
if (_aboveWidget) {
|
|
|
|
_aboveWidget->resizeToWidth(newWidth);
|
|
|
|
_aboveWidget->moveToLeft(0, 0, newWidth);
|
|
|
|
if (showingSearch()) {
|
|
|
|
_aboveWidget->hide();
|
|
|
|
} else {
|
|
|
|
_aboveWidget->show();
|
|
|
|
_aboveHeight = _aboveWidget->height();
|
|
|
|
}
|
|
|
|
}
|
2017-09-26 17:57:01 +00:00
|
|
|
auto rowsCount = shownRowsCount();
|
2017-08-14 12:48:11 +00:00
|
|
|
auto labelTop = rowsTop() + qMax(1, shownRowsCount()) * _rowHeight;
|
2017-11-16 09:13:17 +00:00
|
|
|
auto labelWidth = newWidth - 2 * st::contactsPadding.left();
|
2017-08-14 12:48:11 +00:00
|
|
|
if (_description) {
|
2017-11-16 09:13:17 +00:00
|
|
|
_description->resizeToWidth(labelWidth);
|
2017-08-14 12:48:11 +00:00
|
|
|
_description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
|
|
|
|
_description->setVisible(!showingSearch());
|
|
|
|
}
|
|
|
|
if (_searchNoResults) {
|
2017-11-16 09:13:17 +00:00
|
|
|
_searchNoResults->resizeToWidth(labelWidth);
|
2017-08-14 12:48:11 +00:00
|
|
|
_searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
|
|
|
|
_searchNoResults->setVisible(showingSearch() && _filterResults.empty() && !_controller->isSearchLoading());
|
|
|
|
}
|
|
|
|
if (_searchLoading) {
|
2017-11-16 09:13:17 +00:00
|
|
|
_searchLoading->resizeToWidth(labelWidth);
|
2017-08-14 12:48:11 +00:00
|
|
|
_searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
|
|
|
|
_searchLoading->setVisible(showingSearch() && _filterResults.empty() && _controller->isSearchLoading());
|
|
|
|
}
|
2017-09-26 17:57:01 +00:00
|
|
|
auto label = labelHeight();
|
|
|
|
return ((label > 0 || rowsCount > 0) ? (labelTop + label) : 0)
|
|
|
|
+ _st.padding.bottom();
|
2017-08-14 12:48:11 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::enterEventHook(QEvent *e) {
|
2017-03-13 15:40:21 +00:00
|
|
|
setMouseTracking(true);
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::leaveEventHook(QEvent *e) {
|
2017-03-13 15:40:21 +00:00
|
|
|
setMouseTracking(false);
|
2018-12-25 12:41:40 +00:00
|
|
|
if (_mouseSelection) {
|
|
|
|
setSelected(Selected());
|
|
|
|
_mouseSelection = false;
|
|
|
|
_lastMousePosition = std::nullopt;
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::mouseMoveEvent(QMouseEvent *e) {
|
2017-10-22 17:06:57 +00:00
|
|
|
handleMouseMove(e->globalPos());
|
|
|
|
}
|
|
|
|
|
2018-12-25 12:41:40 +00:00
|
|
|
void PeerListContent::handleMouseMove(QPoint globalPosition) {
|
|
|
|
if (!_lastMousePosition) {
|
|
|
|
_lastMousePosition = globalPosition;
|
|
|
|
return;
|
|
|
|
} else if (!_mouseSelection
|
|
|
|
&& *_lastMousePosition == globalPosition) {
|
|
|
|
return;
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
2018-12-25 12:41:40 +00:00
|
|
|
selectByMouse(globalPosition);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::mousePressEvent(QMouseEvent *e) {
|
2017-10-22 17:06:57 +00:00
|
|
|
_pressButton = e->button();
|
2018-12-25 12:41:40 +00:00
|
|
|
selectByMouse(e->globalPos());
|
2017-03-13 15:40:21 +00:00
|
|
|
setPressed(_selected);
|
2017-04-27 19:04:45 +00:00
|
|
|
if (auto row = getRow(_selected.index)) {
|
|
|
|
auto updateCallback = [this, row, hint = _selected.index] {
|
|
|
|
updateRow(row, hint);
|
|
|
|
};
|
|
|
|
if (_selected.action) {
|
|
|
|
auto actionRect = getActionRect(row, _selected.index);
|
|
|
|
if (!actionRect.isEmpty()) {
|
|
|
|
auto point = mapFromGlobal(QCursor::pos()) - actionRect.topLeft();
|
|
|
|
row->addActionRipple(point, std::move(updateCallback));
|
|
|
|
}
|
|
|
|
} else {
|
2017-03-14 17:04:28 +00:00
|
|
|
auto size = QSize(width(), _rowHeight);
|
|
|
|
auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index));
|
2017-09-26 17:57:01 +00:00
|
|
|
row->addRipple(_st.item, size, point, std::move(updateCallback));
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
2018-09-20 17:56:45 +00:00
|
|
|
if (anim::Disabled()) {
|
|
|
|
mousePressReleased(e->button());
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::mouseReleaseEvent(QMouseEvent *e) {
|
2017-10-22 17:06:57 +00:00
|
|
|
mousePressReleased(e->button());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
2017-03-14 17:04:28 +00:00
|
|
|
updateRow(_pressed.index);
|
|
|
|
updateRow(_selected.index);
|
2017-03-14 14:16:03 +00:00
|
|
|
|
2017-03-13 15:40:21 +00:00
|
|
|
auto pressed = _pressed;
|
2017-03-14 17:04:28 +00:00
|
|
|
setPressed(Selected());
|
2017-10-22 17:06:57 +00:00
|
|
|
if (button == Qt::LeftButton && pressed == _selected) {
|
2017-03-14 17:04:28 +00:00
|
|
|
if (auto row = getRow(pressed.index)) {
|
2017-03-13 15:40:21 +00:00
|
|
|
if (pressed.action) {
|
2017-03-17 12:05:50 +00:00
|
|
|
_controller->rowActionClicked(row);
|
2017-03-13 15:40:21 +00:00
|
|
|
} else {
|
2017-03-17 12:05:50 +00:00
|
|
|
_controller->rowClicked(row);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-22 17:06:57 +00:00
|
|
|
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
|
2017-10-25 16:32:22 +00:00
|
|
|
if (_contextMenu) {
|
|
|
|
_contextMenu->deleteLater();
|
|
|
|
_contextMenu = nullptr;
|
2017-10-22 17:06:57 +00:00
|
|
|
}
|
2017-10-24 17:11:35 +00:00
|
|
|
setContexted(Selected());
|
2017-10-22 17:06:57 +00:00
|
|
|
if (e->reason() == QContextMenuEvent::Mouse) {
|
|
|
|
handleMouseMove(e->globalPos());
|
|
|
|
}
|
|
|
|
|
2017-10-24 17:11:35 +00:00
|
|
|
setContexted(_selected);
|
2017-10-22 17:06:57 +00:00
|
|
|
if (_pressButton != Qt::LeftButton) {
|
|
|
|
mousePressReleased(_pressButton);
|
|
|
|
}
|
|
|
|
|
2018-02-12 11:10:40 +00:00
|
|
|
if (const auto row = getRow(_contexted.index)) {
|
2018-07-19 14:27:09 +00:00
|
|
|
_contextMenu = _controller->rowContextMenu(this, row);
|
2017-10-25 16:32:22 +00:00
|
|
|
if (_contextMenu) {
|
2018-06-04 15:35:11 +00:00
|
|
|
_contextMenu->setDestroyedCallback(crl::guard(
|
2017-10-22 17:06:57 +00:00
|
|
|
this,
|
|
|
|
[this] {
|
2017-10-24 17:11:35 +00:00
|
|
|
setContexted(Selected());
|
2017-10-22 17:06:57 +00:00
|
|
|
handleMouseMove(QCursor::pos());
|
|
|
|
}));
|
2017-10-25 16:32:22 +00:00
|
|
|
_contextMenu->popup(e->globalPos());
|
2017-10-22 17:06:57 +00:00
|
|
|
e->accept();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::setPressed(Selected pressed) {
|
2017-03-14 17:04:28 +00:00
|
|
|
if (auto row = getRow(_pressed.index)) {
|
|
|
|
row->stopLastRipple();
|
2017-04-27 19:04:45 +00:00
|
|
|
row->stopLastActionRipple();
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
_pressed = pressed;
|
|
|
|
}
|
|
|
|
|
2017-10-22 12:07:57 +00:00
|
|
|
TimeMs PeerListContent::paintRow(Painter &p, TimeMs ms, RowIndex index) {
|
2017-03-14 17:04:28 +00:00
|
|
|
auto row = getRow(index);
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(row != nullptr);
|
2017-12-05 14:07:01 +00:00
|
|
|
|
2017-10-10 20:39:44 +00:00
|
|
|
row->lazyInitialize(_st.item);
|
2017-11-07 17:53:00 +00:00
|
|
|
|
2017-10-22 12:07:57 +00:00
|
|
|
auto refreshStatusAt = row->refreshStatusTime();
|
|
|
|
if (refreshStatusAt >= 0 && ms >= refreshStatusAt) {
|
|
|
|
row->refreshStatus();
|
|
|
|
refreshStatusAt = row->refreshStatusTime();
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
|
|
|
|
auto peer = row->peer();
|
|
|
|
auto user = peer->asUser();
|
2017-10-24 17:11:35 +00:00
|
|
|
auto active = (_contexted.index.value >= 0)
|
|
|
|
? _contexted
|
2017-10-22 17:06:57 +00:00
|
|
|
: (_pressed.index.value >= 0)
|
|
|
|
? _pressed
|
|
|
|
: _selected;
|
2017-03-13 15:40:21 +00:00
|
|
|
auto selected = (active.index == index);
|
|
|
|
auto actionSelected = (selected && active.action);
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
auto &bg = selected
|
|
|
|
? _st.item.button.textBgOver
|
|
|
|
: _st.item.button.textBg;
|
|
|
|
p.fillRect(0, 0, width(), _rowHeight, bg);
|
2017-03-17 12:05:50 +00:00
|
|
|
row->paintRipple(p, ms, 0, 0, width());
|
2017-09-26 17:57:01 +00:00
|
|
|
row->paintUserpic(
|
|
|
|
p,
|
|
|
|
_st.item,
|
|
|
|
ms,
|
|
|
|
_st.item.photoPosition.x(),
|
|
|
|
_st.item.photoPosition.y(),
|
|
|
|
width());
|
2017-03-13 15:40:21 +00:00
|
|
|
|
|
|
|
p.setPen(st::contactsNameFg);
|
|
|
|
|
2017-11-07 17:53:00 +00:00
|
|
|
auto skipRight = _st.item.photoPosition.x();
|
2017-04-27 19:04:45 +00:00
|
|
|
auto actionSize = row->actionSize();
|
|
|
|
auto actionMargins = actionSize.isEmpty() ? QMargins() : row->actionMargins();
|
2017-03-13 15:40:21 +00:00
|
|
|
auto &name = row->name();
|
2017-09-26 17:57:01 +00:00
|
|
|
auto namex = _st.item.namePosition.x();
|
2017-11-07 17:53:00 +00:00
|
|
|
auto namew = width() - namex - skipRight;
|
2017-04-27 19:04:45 +00:00
|
|
|
if (!actionSize.isEmpty()) {
|
2017-11-07 17:53:00 +00:00
|
|
|
namew -= actionMargins.left()
|
|
|
|
+ actionSize.width()
|
|
|
|
+ actionMargins.right()
|
|
|
|
- skipRight;
|
2017-04-27 19:04:45 +00:00
|
|
|
}
|
2017-06-28 05:57:58 +00:00
|
|
|
auto statusw = namew;
|
2017-11-07 17:53:00 +00:00
|
|
|
if (auto iconWidth = row->nameIconWidth()) {
|
|
|
|
namew -= iconWidth;
|
|
|
|
row->paintNameIcon(
|
|
|
|
p,
|
|
|
|
namex + qMin(name.maxWidth(), namew),
|
|
|
|
_st.item.namePosition.y(),
|
2017-11-08 16:45:30 +00:00
|
|
|
width(),
|
|
|
|
selected);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
2017-08-14 12:53:49 +00:00
|
|
|
auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio();
|
|
|
|
p.setPen(anim::pen(st::contactsNameFg, st::contactsNameCheckedFg, nameCheckedRatio));
|
2017-09-26 17:57:01 +00:00
|
|
|
name.drawLeftElided(p, namex, _st.item.namePosition.y(), namew, width());
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-04-27 19:04:45 +00:00
|
|
|
if (!actionSize.isEmpty()) {
|
2017-11-07 17:53:00 +00:00
|
|
|
auto actionLeft = width()
|
|
|
|
- actionMargins.right()
|
|
|
|
- actionSize.width();
|
2017-04-27 19:04:45 +00:00
|
|
|
auto actionTop = actionMargins.top();
|
2017-11-07 17:53:00 +00:00
|
|
|
row->paintAction(
|
|
|
|
p,
|
|
|
|
ms,
|
|
|
|
actionLeft,
|
|
|
|
actionTop,
|
|
|
|
width(),
|
|
|
|
selected,
|
|
|
|
actionSelected);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
p.setFont(st::contactsStatusFont);
|
2017-06-16 15:26:19 +00:00
|
|
|
if (row->isSearchResult() && !_mentionHighlight.isEmpty() && peer->userName().startsWith(_mentionHighlight, Qt::CaseInsensitive)) {
|
2017-03-14 18:38:50 +00:00
|
|
|
auto username = peer->userName();
|
2017-06-28 05:57:58 +00:00
|
|
|
auto availableWidth = statusw;
|
2017-06-16 15:26:19 +00:00
|
|
|
auto highlightedPart = '@' + username.mid(0, _mentionHighlight.size());
|
|
|
|
auto grayedPart = username.mid(_mentionHighlight.size());
|
|
|
|
auto highlightedWidth = st::contactsStatusFont->width(highlightedPart);
|
|
|
|
if (highlightedWidth >= availableWidth || grayedPart.isEmpty()) {
|
|
|
|
if (highlightedWidth > availableWidth) {
|
|
|
|
highlightedPart = st::contactsStatusFont->elided(highlightedPart, availableWidth);
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
2017-09-26 17:57:01 +00:00
|
|
|
p.setPen(_st.item.statusFgActive);
|
|
|
|
p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart);
|
2017-03-14 18:38:50 +00:00
|
|
|
} else {
|
2017-06-16 15:26:19 +00:00
|
|
|
grayedPart = st::contactsStatusFont->elided(grayedPart, availableWidth - highlightedWidth);
|
|
|
|
auto grayedWidth = st::contactsStatusFont->width(grayedPart);
|
2017-09-26 17:57:01 +00:00
|
|
|
p.setPen(_st.item.statusFgActive);
|
|
|
|
p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart);
|
|
|
|
p.setPen(selected ? _st.item.statusFgOver : _st.item.statusFg);
|
|
|
|
p.drawTextLeft(_st.item.statusPosition.x() + highlightedWidth, _st.item.statusPosition.y(), width(), grayedPart);
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
2017-09-26 17:57:01 +00:00
|
|
|
row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected);
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
2017-10-22 12:07:57 +00:00
|
|
|
return (refreshStatusAt - ms);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::selectSkip(int direction) {
|
2017-03-14 17:04:28 +00:00
|
|
|
if (_pressed.index.value >= 0) {
|
2017-03-14 14:16:03 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
_mouseSelection = false;
|
2018-12-25 12:41:40 +00:00
|
|
|
_lastMousePosition = std::nullopt;
|
2017-03-13 15:40:21 +00:00
|
|
|
|
2017-03-14 17:04:28 +00:00
|
|
|
auto newSelectedIndex = _selected.index.value + direction;
|
2017-03-14 14:16:03 +00:00
|
|
|
|
2017-03-14 17:04:28 +00:00
|
|
|
auto rowsCount = shownRowsCount();
|
|
|
|
auto index = 0;
|
|
|
|
auto firstEnabled = -1, lastEnabled = -1;
|
2017-08-17 08:31:24 +00:00
|
|
|
enumerateShownRows([&firstEnabled, &lastEnabled, &index](not_null<PeerListRow*> row) {
|
2017-03-14 14:16:03 +00:00
|
|
|
if (!row->disabled()) {
|
2017-03-14 17:04:28 +00:00
|
|
|
if (firstEnabled < 0) {
|
|
|
|
firstEnabled = index;
|
|
|
|
}
|
|
|
|
lastEnabled = index;
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
2017-03-14 17:04:28 +00:00
|
|
|
++index;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
if (firstEnabled < 0) {
|
|
|
|
firstEnabled = rowsCount;
|
|
|
|
lastEnabled = firstEnabled - 1;
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
|
|
|
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(lastEnabled < rowsCount);
|
|
|
|
Assert(firstEnabled - 1 <= lastEnabled);
|
2017-03-14 14:16:03 +00:00
|
|
|
|
|
|
|
// Always pass through the first enabled item when changing from / to none selected.
|
2017-03-14 17:04:28 +00:00
|
|
|
if ((_selected.index.value > firstEnabled && newSelectedIndex < firstEnabled)
|
|
|
|
|| (_selected.index.value < firstEnabled && newSelectedIndex > firstEnabled)) {
|
2017-03-14 14:16:03 +00:00
|
|
|
newSelectedIndex = firstEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Snap the index.
|
|
|
|
newSelectedIndex = snap(newSelectedIndex, firstEnabled - 1, lastEnabled);
|
|
|
|
|
|
|
|
// Skip the disabled rows.
|
|
|
|
if (newSelectedIndex < firstEnabled) {
|
|
|
|
newSelectedIndex = -1;
|
|
|
|
} else if (newSelectedIndex > lastEnabled) {
|
|
|
|
newSelectedIndex = lastEnabled;
|
2017-03-14 17:04:28 +00:00
|
|
|
} else if (getRow(RowIndex(newSelectedIndex))->disabled()) {
|
2017-03-14 14:16:03 +00:00
|
|
|
auto delta = (direction > 0) ? 1 : -1;
|
|
|
|
for (newSelectedIndex += delta; ; newSelectedIndex += delta) {
|
|
|
|
// We must find an enabled row, firstEnabled <= us <= lastEnabled.
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(newSelectedIndex >= 0 && newSelectedIndex < rowsCount);
|
2017-03-14 17:04:28 +00:00
|
|
|
if (!getRow(RowIndex(newSelectedIndex))->disabled()) {
|
2017-03-14 14:16:03 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-14 14:16:03 +00:00
|
|
|
|
2017-03-14 17:04:28 +00:00
|
|
|
_selected.index.value = newSelectedIndex;
|
2017-03-13 15:40:21 +00:00
|
|
|
_selected.action = false;
|
|
|
|
if (newSelectedIndex >= 0) {
|
2017-03-14 17:04:28 +00:00
|
|
|
auto top = (newSelectedIndex > 0) ? getRowTop(RowIndex(newSelectedIndex)) : 0;
|
|
|
|
auto bottom = (newSelectedIndex + 1 < rowsCount) ? getRowTop(RowIndex(newSelectedIndex + 1)) : height();
|
2017-09-26 17:57:01 +00:00
|
|
|
_scrollToRequests.fire({ top, bottom });
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::selectSkipPage(int height, int direction) {
|
2017-03-14 14:16:03 +00:00
|
|
|
auto rowsToSkip = height / _rowHeight;
|
2017-03-13 15:40:21 +00:00
|
|
|
if (!rowsToSkip) return;
|
2017-03-14 14:16:03 +00:00
|
|
|
selectSkip(rowsToSkip * direction);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::loadProfilePhotos() {
|
2017-03-13 15:40:21 +00:00
|
|
|
if (_visibleTop >= _visibleBottom) return;
|
|
|
|
|
|
|
|
auto yFrom = _visibleTop;
|
|
|
|
auto yTo = _visibleBottom + (_visibleBottom - _visibleTop) * PreloadHeightsCount;
|
2017-08-04 14:54:32 +00:00
|
|
|
Auth().downloader().clearPriorities();
|
2017-03-13 15:40:21 +00:00
|
|
|
|
|
|
|
if (yTo < 0) return;
|
|
|
|
if (yFrom < 0) yFrom = 0;
|
|
|
|
|
2017-03-14 17:04:28 +00:00
|
|
|
auto rowsCount = shownRowsCount();
|
|
|
|
if (rowsCount > 0) {
|
2017-03-13 15:40:21 +00:00
|
|
|
auto from = yFrom / _rowHeight;
|
|
|
|
if (from < 0) from = 0;
|
2017-03-14 17:04:28 +00:00
|
|
|
if (from < rowsCount) {
|
2017-03-13 15:40:21 +00:00
|
|
|
auto to = (yTo / _rowHeight) + 1;
|
2017-03-14 17:04:28 +00:00
|
|
|
if (to > rowsCount) to = rowsCount;
|
2017-03-13 15:40:21 +00:00
|
|
|
|
|
|
|
for (auto index = from; index != to; ++index) {
|
2017-03-14 17:04:28 +00:00
|
|
|
getRow(RowIndex(index))->peer()->loadUserpic();
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::checkScrollForPreload() {
|
2017-10-10 20:39:44 +00:00
|
|
|
if (_visibleBottom + PreloadHeightsCount * (_visibleBottom - _visibleTop) >= height()) {
|
2017-06-14 10:46:06 +00:00
|
|
|
_controller->loadMoreRows();
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::searchQueryChanged(QString query) {
|
2017-07-06 11:37:42 +00:00
|
|
|
auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
2017-10-24 17:11:35 +00:00
|
|
|
auto normalizedQuery = searchWordsList.join(' ');
|
2017-06-16 15:26:19 +00:00
|
|
|
if (_normalizedSearchQuery != normalizedQuery) {
|
|
|
|
setSearchQuery(query, normalizedQuery);
|
2017-06-13 21:08:47 +00:00
|
|
|
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
|
2017-08-17 08:31:24 +00:00
|
|
|
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
|
2017-03-14 17:04:28 +00:00
|
|
|
for_const (auto &searchWord, searchWordsList) {
|
|
|
|
auto searchWordStart = searchWord[0].toLower();
|
|
|
|
auto it = _searchIndex.find(searchWordStart);
|
|
|
|
if (it == _searchIndex.cend()) {
|
|
|
|
// Some word can't be found in any row.
|
|
|
|
minimalList = nullptr;
|
|
|
|
break;
|
|
|
|
} else if (!minimalList || minimalList->size() > it->second.size()) {
|
|
|
|
minimalList = &it->second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (minimalList) {
|
2017-10-24 17:11:35 +00:00
|
|
|
auto searchWordInNames = [](
|
|
|
|
not_null<PeerData*> peer,
|
|
|
|
const QString &searchWord) {
|
|
|
|
for (auto &nameWord : peer->nameWords()) {
|
2017-03-14 17:04:28 +00:00
|
|
|
if (nameWord.startsWith(searchWord)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
2017-10-24 17:11:35 +00:00
|
|
|
auto allSearchWordsInNames = [&](
|
|
|
|
not_null<PeerData*> peer) {
|
2017-03-14 17:04:28 +00:00
|
|
|
for_const (auto &searchWord, searchWordsList) {
|
|
|
|
if (!searchWordInNames(peer, searchWord)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
_filterResults.reserve(minimalList->size());
|
|
|
|
for_const (auto row, *minimalList) {
|
|
|
|
if (allSearchWordsInNames(row->peer())) {
|
|
|
|
_filterResults.push_back(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-19 08:18:20 +00:00
|
|
|
if (_controller->hasComplexSearch()) {
|
2017-06-13 21:08:47 +00:00
|
|
|
_controller->search(_searchQuery);
|
2017-03-14 18:38:50 +00:00
|
|
|
}
|
2017-03-14 17:04:28 +00:00
|
|
|
refreshRows();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-24 17:11:35 +00:00
|
|
|
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
|
|
|
auto result = std::make_unique<PeerListState>();
|
2017-10-25 08:38:55 +00:00
|
|
|
result->controllerState
|
|
|
|
= std::make_unique<PeerListController::SavedStateBase>();
|
2017-10-24 17:11:35 +00:00
|
|
|
result->list.reserve(_rows.size());
|
|
|
|
for (auto &row : _rows) {
|
|
|
|
result->list.push_back(row->peer());
|
|
|
|
}
|
|
|
|
result->filterResults.reserve(_filterResults.size());
|
|
|
|
for (auto &row : _filterResults) {
|
|
|
|
result->filterResults.push_back(row->peer());
|
|
|
|
}
|
|
|
|
result->searchQuery = _searchQuery;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListContent::restoreState(
|
|
|
|
std::unique_ptr<PeerListState> state) {
|
2017-10-25 08:38:55 +00:00
|
|
|
if (!state || !state->controllerState) {
|
2017-10-24 17:11:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
clearAllContent();
|
|
|
|
|
|
|
|
for (auto peer : state->list) {
|
|
|
|
if (auto row = _controller->createRestoredRow(peer)) {
|
|
|
|
appendRow(std::move(row));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto query = state->searchQuery;
|
|
|
|
auto searchWords = TextUtilities::PrepareSearchWords(query);
|
|
|
|
setSearchQuery(query, searchWords.join(' '));
|
|
|
|
for (auto peer : state->filterResults) {
|
|
|
|
if (auto existingRow = findRow(peer->id)) {
|
|
|
|
_filterResults.push_back(existingRow);
|
|
|
|
} else if (auto row = _controller->createSearchRow(peer)) {
|
|
|
|
appendSearchRow(std::move(row));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
refreshRows();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerListContent::setSearchQuery(
|
|
|
|
const QString &query,
|
|
|
|
const QString &normalizedQuery) {
|
2017-06-16 15:26:19 +00:00
|
|
|
setSelected(Selected());
|
|
|
|
setPressed(Selected());
|
2017-10-24 17:11:35 +00:00
|
|
|
setContexted(Selected());
|
2018-12-25 12:41:40 +00:00
|
|
|
_mouseSelection = false;
|
|
|
|
_lastMousePosition = std::nullopt;
|
2017-06-16 15:26:19 +00:00
|
|
|
_searchQuery = query;
|
|
|
|
_normalizedSearchQuery = normalizedQuery;
|
2017-10-24 17:11:35 +00:00
|
|
|
_mentionHighlight = _searchQuery.startsWith('@')
|
|
|
|
? _searchQuery.mid(1)
|
|
|
|
: _searchQuery;
|
2017-06-16 15:26:19 +00:00
|
|
|
_filterResults.clear();
|
|
|
|
clearSearchRows();
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::submitted() {
|
2017-12-08 09:30:30 +00:00
|
|
|
if (const auto row = getRow(_selected.index)) {
|
2017-03-17 12:05:50 +00:00
|
|
|
_controller->rowClicked(row);
|
2017-12-08 09:30:30 +00:00
|
|
|
} else if (showingSearch()) {
|
|
|
|
if (const auto row = getRow(RowIndex(0))) {
|
|
|
|
_controller->rowClicked(row);
|
|
|
|
}
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::visibleTopBottomUpdated(
|
2017-09-13 16:57:44 +00:00
|
|
|
int visibleTop,
|
|
|
|
int visibleBottom) {
|
2017-03-13 15:40:21 +00:00
|
|
|
_visibleTop = visibleTop;
|
|
|
|
_visibleBottom = visibleBottom;
|
|
|
|
loadProfilePhotos();
|
|
|
|
checkScrollForPreload();
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::setSelected(Selected selected) {
|
2017-03-14 17:04:28 +00:00
|
|
|
updateRow(_selected.index);
|
2017-04-07 15:07:26 +00:00
|
|
|
if (_selected != selected) {
|
|
|
|
_selected = selected;
|
|
|
|
updateRow(_selected.index);
|
|
|
|
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
|
|
|
|
}
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
|
|
|
|
2017-10-24 17:11:35 +00:00
|
|
|
void PeerListContent::setContexted(Selected contexted) {
|
|
|
|
updateRow(_contexted.index);
|
|
|
|
if (_contexted != contexted) {
|
|
|
|
_contexted = contexted;
|
|
|
|
updateRow(_contexted.index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::restoreSelection() {
|
2018-12-25 12:41:40 +00:00
|
|
|
if (_mouseSelection) {
|
|
|
|
selectByMouse(QCursor::pos());
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-10-24 17:11:35 +00:00
|
|
|
auto PeerListContent::saveSelectedData(Selected from)
|
|
|
|
-> SelectedSaved {
|
|
|
|
if (auto row = getRow(from.index)) {
|
|
|
|
return { row->id(), from };
|
|
|
|
}
|
|
|
|
return { PeerListRowId(0), from };
|
|
|
|
}
|
|
|
|
|
|
|
|
auto PeerListContent::restoreSelectedData(SelectedSaved from)
|
|
|
|
-> Selected {
|
|
|
|
auto result = from.old;
|
|
|
|
if (auto row = findRow(from.id)) {
|
|
|
|
result.index = findRowIndex(row, result.index);
|
|
|
|
} else {
|
|
|
|
result.index.value = -1;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-12-25 12:41:40 +00:00
|
|
|
void PeerListContent::selectByMouse(QPoint globalPosition) {
|
|
|
|
_mouseSelection = true;
|
|
|
|
_lastMousePosition = globalPosition;
|
|
|
|
const auto point = mapFromGlobal(globalPosition);
|
|
|
|
auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(globalPosition));
|
2017-03-14 17:04:28 +00:00
|
|
|
auto selected = Selected();
|
2017-08-14 12:48:11 +00:00
|
|
|
auto rowsPointY = point.y() - rowsTop();
|
2017-04-07 15:07:26 +00:00
|
|
|
selected.index.value = (in && rowsPointY >= 0 && rowsPointY < shownRowsCount() * _rowHeight) ? (rowsPointY / _rowHeight) : -1;
|
2017-03-14 17:04:28 +00:00
|
|
|
if (selected.index.value >= 0) {
|
|
|
|
auto row = getRow(selected.index);
|
2017-03-14 14:16:03 +00:00
|
|
|
if (row->disabled()) {
|
2017-03-14 17:04:28 +00:00
|
|
|
selected = Selected();
|
2017-03-14 14:16:03 +00:00
|
|
|
} else {
|
2017-04-27 19:04:45 +00:00
|
|
|
if (getActionRect(row, selected.index).contains(point)) {
|
2017-03-14 14:16:03 +00:00
|
|
|
selected.action = true;
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-07 15:07:26 +00:00
|
|
|
setSelected(selected);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
QRect PeerListContent::getActionRect(not_null<PeerListRow*> row, RowIndex index) const {
|
2017-04-27 19:04:45 +00:00
|
|
|
auto actionSize = row->actionSize();
|
|
|
|
if (actionSize.isEmpty()) {
|
|
|
|
return QRect();
|
|
|
|
}
|
|
|
|
auto actionMargins = row->actionMargins();
|
2017-11-07 17:53:00 +00:00
|
|
|
auto actionRight = actionMargins.right();
|
2017-04-27 19:04:45 +00:00
|
|
|
auto actionTop = actionMargins.top();
|
|
|
|
auto actionLeft = width() - actionRight - actionSize.width();
|
|
|
|
auto rowTop = getRowTop(index);
|
|
|
|
return myrtlrect(actionLeft, rowTop + actionTop, actionSize.width(), actionSize.height());
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
int PeerListContent::rowsTop() const {
|
|
|
|
return _aboveHeight + _st.padding.top();
|
2017-08-14 12:48:11 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
int PeerListContent::getRowTop(RowIndex index) const {
|
2017-03-14 17:04:28 +00:00
|
|
|
if (index.value >= 0) {
|
2017-08-14 12:48:11 +00:00
|
|
|
return rowsTop() + index.value * _rowHeight;
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::updateRow(not_null<PeerListRow*> row, RowIndex hint) {
|
2017-03-14 17:04:28 +00:00
|
|
|
updateRow(findRowIndex(row, hint));
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::updateRow(RowIndex index) {
|
2017-03-17 12:05:50 +00:00
|
|
|
if (index.value < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto row = getRow(index);
|
|
|
|
if (row->disabled()) {
|
|
|
|
if (index == _selected.index) {
|
|
|
|
setSelected(Selected());
|
|
|
|
}
|
|
|
|
if (index == _pressed.index) {
|
|
|
|
setPressed(Selected());
|
2017-03-14 17:04:28 +00:00
|
|
|
}
|
2017-10-24 17:11:35 +00:00
|
|
|
if (index == _contexted.index) {
|
|
|
|
setContexted(Selected());
|
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
2017-03-17 12:05:50 +00:00
|
|
|
update(0, getRowTop(index), width(), _rowHeight);
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
|
2017-03-14 17:04:28 +00:00
|
|
|
template <typename Callback>
|
2017-09-26 17:57:01 +00:00
|
|
|
bool PeerListContent::enumerateShownRows(Callback callback) {
|
2017-03-14 17:04:28 +00:00
|
|
|
return enumerateShownRows(0, shownRowsCount(), std::move(callback));
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Callback>
|
2017-09-26 17:57:01 +00:00
|
|
|
bool PeerListContent::enumerateShownRows(int from, int to, Callback callback) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(0 <= from);
|
|
|
|
Assert(from <= to);
|
2017-03-14 17:04:28 +00:00
|
|
|
if (showingSearch()) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(to <= _filterResults.size());
|
2017-03-14 17:04:28 +00:00
|
|
|
for (auto i = from; i != to; ++i) {
|
|
|
|
if (!callback(_filterResults[i])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(to <= _rows.size());
|
2017-03-14 17:04:28 +00:00
|
|
|
for (auto i = from; i != to; ++i) {
|
|
|
|
if (!callback(_rows[i].get())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
PeerListRow *PeerListContent::getRow(RowIndex index) {
|
2017-03-14 17:04:28 +00:00
|
|
|
if (index.value >= 0) {
|
|
|
|
if (showingSearch()) {
|
|
|
|
if (index.value < _filterResults.size()) {
|
|
|
|
return _filterResults[index.value];
|
|
|
|
}
|
|
|
|
} else if (index.value < _rows.size()) {
|
|
|
|
return _rows[index.value].get();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-10-24 17:11:35 +00:00
|
|
|
PeerListContent::RowIndex PeerListContent::findRowIndex(
|
|
|
|
not_null<PeerListRow*> row,
|
|
|
|
RowIndex hint) {
|
2017-03-14 17:04:28 +00:00
|
|
|
if (!showingSearch()) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(!row->isSearchResult());
|
2017-03-14 17:04:28 +00:00
|
|
|
return RowIndex(row->absoluteIndex());
|
|
|
|
}
|
|
|
|
|
|
|
|
auto result = hint;
|
|
|
|
if (getRow(result) == row) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto count = shownRowsCount();
|
|
|
|
for (result.value = 0; result.value != count; ++result.value) {
|
|
|
|
if (getRow(result) == row) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.value = -1;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void PeerListContent::handleNameChanged(const Notify::PeerUpdate &update) {
|
2017-08-11 10:42:52 +00:00
|
|
|
auto byPeer = _rowsByPeer.find(update.peer);
|
2017-04-27 19:04:45 +00:00
|
|
|
if (byPeer != _rowsByPeer.cend()) {
|
|
|
|
for (auto row : byPeer->second) {
|
|
|
|
if (addingToSearchIndex()) {
|
|
|
|
addToSearchIndex(row);
|
|
|
|
}
|
2017-10-10 20:39:44 +00:00
|
|
|
row->refreshName(_st.item);
|
2017-04-27 19:04:45 +00:00
|
|
|
updateRow(row);
|
2017-03-14 14:16:03 +00:00
|
|
|
}
|
2017-03-13 15:40:21 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-01 16:34:44 +00:00
|
|
|
|
|
|
|
PeerListContent::~PeerListContent() {
|
|
|
|
if (_contextMenu) {
|
|
|
|
_contextMenu->setDestroyedCallback(nullptr);
|
|
|
|
}
|
|
|
|
}
|