From 46dab1a6b441555c758d986ca7476f5af80d2a5f Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 Mar 2017 20:04:28 +0300 Subject: [PATCH] Add local search (filter) in block user box. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/boxes.style | 6 + Telegram/SourceFiles/boxes/peer_list_box.cpp | 413 +++++++++++++----- Telegram/SourceFiles/boxes/peer_list_box.h | 100 +++-- .../settings_blocked_box_controller.cpp | 6 +- 5 files changed, 379 insertions(+), 147 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3449424f71..7cf3b14167 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -437,6 +437,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_blocked_list_add_title" = "Select user to block"; "lng_blocked_list_already_blocked" = "blocked already"; "lng_blocked_list_about" = "Blocked users can't send you messages or add you to groups. They will not see your profile pictures, online and last seen status."; +"lng_blocked_list_not_found" = "No users found."; "lng_preview_loading" = "Getting Link Info..."; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index fe63c860f7..fe22dc9414 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -373,6 +373,12 @@ notificationSampleOpacity: 0.5; notificationSampleSize: size(64px, 16px); membersAboutLimitPadding: margins(0px, 12px, 0px, 12px); +membersAbout: FlatLabel(defaultFlatLabel) { + width: 332px; + textFg: membersAboutLimitFg; + align: align(top); + style: boxLabelStyle; +} sessionsScroll: boxLayerScroll; sessionsHeight: 350px; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index e880b66b99..d63f1f4387 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -29,6 +29,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "storage/file_download.h" #include "ui/widgets/multi_select.h" +#include "ui/widgets/labels.h" #include "ui/effects/widget_slide_wrap.h" #include "lang.h" @@ -78,11 +79,18 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) { _inner->selectSkipPage(height(), 1); } else if (e->key() == Qt::Key_PageUp) { _inner->selectSkipPage(height(), -1); + } else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) { + _select->entity()->clearQuery(); } else { BoxContent::keyPressEvent(e); } } +void PeerListBox::searchQueryChanged(const QString &query) { + onScrollToY(0); + _inner->searchQueryChanged(query); +} + void PeerListBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); @@ -124,12 +132,20 @@ void PeerListBox::removeRow(Row *row) { _inner->removeRow(row); } -int PeerListBox::rowsCount() const { - return _inner->rowsCount(); +int PeerListBox::fullRowsCount() const { + return _inner->fullRowsCount(); } void PeerListBox::setAboutText(const QString &aboutText) { - _inner->setAboutText(aboutText); + if (aboutText.isEmpty()) { + setAbout(nullptr); + } else { + setAbout(object_ptr(this, aboutText, Ui::FlatLabel::InitType::Simple, st::membersAbout)); + } +} + +void PeerListBox::setAbout(object_ptr about) { + _inner->setAbout(std::move(about)); } void PeerListBox::refreshRows() { @@ -141,6 +157,8 @@ void PeerListBox::setSearchable(bool searchable) { if (searchable) { if (!_select) { _select = createMultiSelect(); + _select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { _inner->submitted(); }); + _select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); }); _select->resizeToWidth(width()); _select->moveToLeft(0, 0); } @@ -150,6 +168,18 @@ void PeerListBox::setSearchable(bool searchable) { } } +void PeerListBox::setSearchNoResultsText(const QString &searchNoResultsText) { + if (searchNoResultsText.isEmpty()) { + setSearchNoResults(nullptr); + } else { + setSearchNoResults(object_ptr(this, searchNoResultsText, Ui::FlatLabel::InitType::Simple, st::membersAbout)); + } +} + +void PeerListBox::setSearchNoResults(object_ptr searchNoResults) { + _inner->setSearchNoResults(std::move(searchNoResults)); +} + PeerListBox::Row::Row(PeerData *peer) : _peer(peer) { } @@ -249,9 +279,7 @@ void PeerListBox::Row::lazyInitialize() { PeerListBox::Inner::Inner(QWidget *parent, Controller *controller) : TWidget(parent) , _controller(controller) -, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) -, _aboutWidth(st::boxWideWidth - st::contactsPadding.left() - st::contactsPadding.right()) -, _about(_aboutWidth) { +, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) { subscribe(AuthSession::CurrentDownloaderTaskFinished(), [this] { update(); }); connect(App::main(), SIGNAL(peerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&))); @@ -260,7 +288,7 @@ PeerListBox::Inner::Inner(QWidget *parent, Controller *controller) : TWidget(par void PeerListBox::Inner::appendRow(std::unique_ptr row) { if (_rowsByPeer.find(row->peer()) == _rowsByPeer.cend()) { - row->setIndex(_rows.size()); + row->setAbsoluteIndex(_rows.size()); addRowEntry(row.get()); _rows.push_back(std::move(row)); } @@ -300,7 +328,7 @@ void PeerListBox::Inner::removeFromSearchIndex(Row *row) { void PeerListBox::Inner::prependRow(std::unique_ptr row) { if (_rowsByPeer.find(row->peer()) == _rowsByPeer.cend()) { - _rowsByPeer.emplace(row->peer(), row.get()); + addRowEntry(row.get()); _rows.insert(_rows.begin(), std::move(row)); refreshIndices(); } @@ -309,7 +337,7 @@ void PeerListBox::Inner::prependRow(std::unique_ptr row) { void PeerListBox::Inner::refreshIndices() { auto index = 0; for (auto &row : _rows) { - row->setIndex(index++); + row->setAbsoluteIndex(index++); } } @@ -318,57 +346,61 @@ PeerListBox::Row *PeerListBox::Inner::findRow(PeerData *peer) { return (it == _rowsByPeer.cend()) ? nullptr : it->second; } -void PeerListBox::Inner::updateRow(Row *row) { - auto index = row->index(); - if (row->disabled()) { - if (index == _selected.index) { - _selected = SelectedRow(); - } - if (index == _pressed.index) { - setPressed(SelectedRow()); - } - } - updateRowWithIndex(index); -} - void PeerListBox::Inner::removeRow(Row *row) { - auto index = row->index(); + auto index = row->absoluteIndex(); t_assert(index >= 0 && index < _rows.size()); t_assert(_rows[index].get() == row); - clearSelection(); + setSelected(Selected()); + setPressed(Selected()); _rowsByPeer.erase(row->peer()); if (_searchable) { removeFromSearchIndex(row); } + _filterResults.erase(std::find(_filterResults.begin(), _filterResults.end(), row), _filterResults.end()); _rows.erase(_rows.begin() + index); for (auto i = index, count = int(_rows.size()); i != count; ++i) { - _rows[i]->setIndex(i); + _rows[i]->setAbsoluteIndex(i); } + + restoreSelection(); } -int PeerListBox::Inner::rowsCount() const { +int PeerListBox::Inner::fullRowsCount() const { return _rows.size(); } -void PeerListBox::Inner::setAboutText(const QString &aboutText) { - if (_about.isEmpty() && aboutText.isEmpty()) { - return; +void PeerListBox::Inner::setAbout(object_ptr about) { + _about = std::move(about); + if (_about) { + _about->setParent(this); } - _about.setText(st::boxLabelStyle, aboutText); +} + +int PeerListBox::Inner::labelHeight() const { + if (showingSearch()) { + if (_filterResults.empty() && _searchNoResults) { + return st::membersAboutLimitPadding.top() + _searchNoResults->height() + st::membersAboutLimitPadding.bottom(); + } + return 0; + } + if (_about) { + return st::membersAboutLimitPadding.top() + _about->height() + st::membersAboutLimitPadding.bottom(); + } + return 0; } void PeerListBox::Inner::refreshRows() { - if (!_about.isEmpty()) { - _aboutHeight = st::membersAboutLimitPadding.top() + _about.countHeight(_aboutWidth) + st::membersAboutLimitPadding.bottom(); - } else { - _aboutHeight = 0; + auto labelTop = st::membersMarginTop + qMax(1, shownRowsCount()) * _rowHeight; + resize(width(), labelTop + labelHeight() + st::membersMarginBottom); + if (_about) { + _about->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top()); + _about->setVisible(!showingSearch()); } - if (_rows.empty()) { - resize(width(), st::membersMarginTop + _rowHeight + _aboutHeight + st::membersMarginBottom); - } else { - resize(width(), st::membersMarginTop + _rows.size() * _rowHeight + _aboutHeight + st::membersMarginBottom); + if (_searchNoResults) { + _searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top()); + _searchNoResults->setVisible(showingSearch() && _filterResults.empty()); } if (_visibleBottom > 0) { checkScrollForPreload(); @@ -386,6 +418,13 @@ void PeerListBox::Inner::setSearchable(bool searchable) { } } +void PeerListBox::Inner::setSearchNoResults(object_ptr searchNoResults) { + _searchNoResults = std::move(searchNoResults); + if (_searchNoResults) { + _searchNoResults->setParent(this); + } +} + void PeerListBox::Inner::paintEvent(QPaintEvent *e) { QRect r(e->rect()); Painter p(this); @@ -396,23 +435,15 @@ void PeerListBox::Inner::paintEvent(QPaintEvent *e) { auto yFrom = r.y() - st::membersMarginTop; auto yTo = r.y() + r.height() - st::membersMarginTop; p.translate(0, st::membersMarginTop); - if (_rows.empty()) { - if (!_about.isEmpty()) { - p.setPen(st::membersAboutLimitFg); - _about.draw(p, st::contactsPadding.left(), _rowHeight + st::membersAboutLimitPadding.top(), _aboutWidth, style::al_center); - } - } else { - auto from = floorclamp(yFrom, _rowHeight, 0, _rows.size()); - auto to = ceilclamp(yTo, _rowHeight, 0, _rows.size()); + auto count = shownRowsCount(); + if (count > 0) { + auto from = floorclamp(yFrom, _rowHeight, 0, count); + auto to = ceilclamp(yTo, _rowHeight, 0, count); p.translate(0, from * _rowHeight); for (auto index = from; index != to; ++index) { - paintRow(p, ms, index); + paintRow(p, ms, RowIndex(index)); p.translate(0, _rowHeight); } - if (!_about.isEmpty() && to == _rows.size()) { - p.setPen(st::membersAboutLimitFg); - _about.draw(p, st::contactsPadding.left(), st::membersAboutLimitPadding.top(), _aboutWidth, style::al_center); - } } } @@ -423,9 +454,7 @@ void PeerListBox::Inner::enterEventHook(QEvent *e) { void PeerListBox::Inner::leaveEventHook(QEvent *e) { _mouseSelection = false; setMouseTracking(false); - if (_selected.index >= 0) { - clearSelection(); - } + setSelected(Selected()); } void PeerListBox::Inner::mouseMoveEvent(QMouseEvent *e) { @@ -443,48 +472,51 @@ void PeerListBox::Inner::mousePressEvent(QMouseEvent *e) { updateSelection(); setPressed(_selected); - if (_selected.index >= 0 && _selected.index < _rows.size() && !_selected.action) { - auto size = QSize(width(), _rowHeight); - auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index)); - auto row = _rows[_selected.index].get(); - row->addRipple(size, point, [this, row] { - updateRow(row); - }); + if (!_selected.action) { + if (auto row = getRow(_selected.index)) { + auto size = QSize(width(), _rowHeight); + auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index)); + auto hint = _selected.index; + row->addRipple(size, point, [this, row, hint] { + updateRow(row, hint); + }); + } } } void PeerListBox::Inner::mouseReleaseEvent(QMouseEvent *e) { - updateRowWithIndex(_pressed.index); - updateRowWithIndex(_selected.index); + updateRow(_pressed.index); + updateRow(_selected.index); auto pressed = _pressed; - setPressed(SelectedRow()); - if (e->button() == Qt::LeftButton) { - if (pressed == _selected && pressed.index >= 0) { + setPressed(Selected()); + if (e->button() == Qt::LeftButton && pressed == _selected) { + if (auto row = getRow(pressed.index)) { + auto peer = row->peer(); if (pressed.action) { - _controller->rowActionClicked(_rows[pressed.index]->peer()); + _controller->rowActionClicked(peer); } else { - _controller->rowClicked(_rows[pressed.index]->peer()); + _controller->rowClicked(peer); } } } } -void PeerListBox::Inner::setPressed(SelectedRow pressed) { - if (_pressed.index >= 0 && _pressed.index < _rows.size()) { - _rows[_pressed.index]->stopLastRipple(); +void PeerListBox::Inner::setPressed(Selected pressed) { + if (auto row = getRow(_pressed.index)) { + row->stopLastRipple(); } _pressed = pressed; } -void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, int index) { - t_assert(index >= 0 && index < _rows.size()); - auto row = _rows[index].get(); +void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) { + auto row = getRow(index); + t_assert(row != nullptr); row->lazyInitialize(); auto peer = row->peer(); auto user = peer->asUser(); - auto active = (_pressed.index >= 0) ? _pressed : _selected; + auto active = (_pressed.index.value >= 0) ? _pressed : _selected; auto selected = (active.index == index); auto actionSelected = (selected && active.action); @@ -520,33 +552,37 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, int index) { } void PeerListBox::Inner::selectSkip(int direction) { - if (_pressed.index >= 0) { + if (_pressed.index.value >= 0) { return; } _mouseSelection = false; - auto newSelectedIndex = _selected.index + direction; + auto newSelectedIndex = _selected.index.value + direction; - auto firstEnabled = 0; - for_const (auto &row, _rows) { + auto rowsCount = shownRowsCount(); + auto index = 0; + auto firstEnabled = -1, lastEnabled = -1; + enumerateShownRows([&firstEnabled, &lastEnabled, &index](Row *row) { if (!row->disabled()) { - break; - } - ++firstEnabled; - } - auto lastEnabled = int(_rows.size()) - 1; - for (; lastEnabled > firstEnabled; --lastEnabled) { - if (!_rows[lastEnabled]->disabled()) { - break; + if (firstEnabled < 0) { + firstEnabled = index; + } + lastEnabled = index; } + ++index; + return true; + }); + if (firstEnabled < 0) { + firstEnabled = rowsCount; + lastEnabled = firstEnabled - 1; } - t_assert(lastEnabled < _rows.size()); + t_assert(lastEnabled < rowsCount); t_assert(firstEnabled - 1 <= lastEnabled); // Always pass through the first enabled item when changing from / to none selected. - if ((_selected.index > firstEnabled && newSelectedIndex < firstEnabled) - || (_selected.index < firstEnabled && newSelectedIndex > firstEnabled)) { + if ((_selected.index.value > firstEnabled && newSelectedIndex < firstEnabled) + || (_selected.index.value < firstEnabled && newSelectedIndex > firstEnabled)) { newSelectedIndex = firstEnabled; } @@ -558,22 +594,22 @@ void PeerListBox::Inner::selectSkip(int direction) { newSelectedIndex = -1; } else if (newSelectedIndex > lastEnabled) { newSelectedIndex = lastEnabled; - } else if (_rows[newSelectedIndex]->disabled()) { + } else if (getRow(RowIndex(newSelectedIndex))->disabled()) { auto delta = (direction > 0) ? 1 : -1; for (newSelectedIndex += delta; ; newSelectedIndex += delta) { // We must find an enabled row, firstEnabled <= us <= lastEnabled. - t_assert(newSelectedIndex >= 0 && newSelectedIndex < _rows.size()); - if (!_rows[newSelectedIndex]->disabled()) { + t_assert(newSelectedIndex >= 0 && newSelectedIndex < rowsCount); + if (!getRow(RowIndex(newSelectedIndex))->disabled()) { break; } } } - _selected.index = newSelectedIndex; + _selected.index.value = newSelectedIndex; _selected.action = false; if (newSelectedIndex >= 0) { - auto top = (newSelectedIndex > 0) ? getRowTop(newSelectedIndex) : 0; - auto bottom = (newSelectedIndex + 1 < _rows.size()) ? getRowTop(newSelectedIndex + 1) : height(); + auto top = (newSelectedIndex > 0) ? getRowTop(RowIndex(newSelectedIndex)) : 0; + auto bottom = (newSelectedIndex + 1 < rowsCount) ? getRowTop(RowIndex(newSelectedIndex + 1)) : height(); emit mustScrollTo(top, bottom); } @@ -596,15 +632,16 @@ void PeerListBox::Inner::loadProfilePhotos() { if (yTo < 0) return; if (yFrom < 0) yFrom = 0; - if (!_rows.empty()) { + auto rowsCount = shownRowsCount(); + if (rowsCount > 0) { auto from = yFrom / _rowHeight; if (from < 0) from = 0; - if (from < _rows.size()) { + if (from < rowsCount) { auto to = (yTo / _rowHeight) + 1; - if (to > _rows.size()) to = _rows.size(); + if (to > rowsCount) to = rowsCount; for (auto index = from; index != to; ++index) { - _rows[index]->peer()->loadUserpic(); + getRow(RowIndex(index))->peer()->loadUserpic(); } } } @@ -616,6 +653,67 @@ void PeerListBox::Inner::checkScrollForPreload() { } } +void PeerListBox::Inner::searchQueryChanged(QString query) { + auto searchWordsList = query.isEmpty() ? QStringList() : query.split(cWordSplit(), QString::SkipEmptyParts); + if (!searchWordsList.isEmpty()) { + query = searchWordsList.join(' '); + } + if (_searchQuery != query) { + setSelected(Selected()); + setPressed(Selected()); + + _searchQuery = query; + _filterResults.clear(); + if (!searchWordsList.isEmpty()) { + auto minimalList = (const std::vector*)nullptr; + 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) { + auto searchWordInNames = [](PeerData *peer, const QString &searchWord) { + for_const (auto &nameWord, peer->names) { + if (nameWord.startsWith(searchWord)) { + return true; + } + } + return false; + }; + auto allSearchWordsInNames = [searchWordInNames, &searchWordsList](PeerData *peer) { + 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); + } + } + } + } + refreshRows(); + restoreSelection(); + } +} + +void PeerListBox::Inner::submitted() { + if (auto row = getRow(_selected.index)) { + _controller->rowClicked(row->peer()); + } +} + void PeerListBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { _visibleTop = visibleTop; _visibleBottom = visibleBottom; @@ -623,9 +721,13 @@ void PeerListBox::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) checkScrollForPreload(); } -void PeerListBox::Inner::clearSelection() { - updateRowWithIndex(_selected.index); - _selected = SelectedRow(); +void PeerListBox::Inner::setSelected(Selected selected) { + updateRow(_selected.index); + _selected = selected; + updateRow(_selected.index); +} + +void PeerListBox::Inner::restoreSelection() { _lastMousePosition = QCursor::pos(); updateSelection(); } @@ -633,21 +735,22 @@ void PeerListBox::Inner::clearSelection() { void PeerListBox::Inner::updateSelection() { if (!_mouseSelection) return; + auto rowsTop = st::membersMarginTop; auto point = mapFromGlobal(_lastMousePosition); - point.setY(point.y() - st::membersMarginTop); auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePosition)); - auto selected = SelectedRow(); - selected.index = (in && point.y() >= 0 && point.y() < _rows.size() * _rowHeight) ? (point.y() / _rowHeight) : -1; - if (selected.index >= 0) { - auto &row = _rows[selected.index]; + auto selected = Selected(); + auto rowsPointY = point.y() - rowsTop; + selected.index.value = (in && rowsPointY >= 0) ? snap(rowsPointY / _rowHeight, 0, shownRowsCount() - 1) : -1; + if (selected.index.value >= 0) { + auto row = getRow(selected.index); if (row->disabled()) { - selected = SelectedRow(); + selected = Selected(); } else { auto actionRight = st::contactsPadding.right() + st::contactsCheckPosition.x(); auto actionTop = (_rowHeight - st::normalFont->height) / 2; - auto actionWidth = _rows[selected.index]->actionWidth(); + auto actionWidth = row->actionWidth(); auto actionLeft = width() - actionWidth - actionRight; - auto rowTop = selected.index * _rowHeight; + auto rowTop = getRowTop(selected.index); auto actionRect = myrtlrect(actionLeft, rowTop + actionTop, actionWidth, st::normalFont->height); if (actionRect.contains(point)) { selected.action = true; @@ -655,9 +758,7 @@ void PeerListBox::Inner::updateSelection() { } } if (_selected != selected) { - updateRowWithIndex(_selected.index); - _selected = selected; - updateRowWithIndex(_selected.index); + setSelected(selected); setCursor(_selected.action ? style::cur_pointer : style::cur_default); } } @@ -666,25 +767,97 @@ void PeerListBox::Inner::peerUpdated(PeerData *peer) { update(); } -int PeerListBox::Inner::getRowTop(int index) const { - if (index >= 0) { - return st::membersMarginTop + index * _rowHeight; +int PeerListBox::Inner::getRowTop(RowIndex index) const { + if (index.value >= 0) { + return st::membersMarginTop + index.value * _rowHeight; } return -1; } -void PeerListBox::Inner::updateRowWithIndex(int index) { - if (index >= 0) { +void PeerListBox::Inner::updateRow(Row *row, RowIndex hint) { + updateRow(findRowIndex(row, hint)); +} + +void PeerListBox::Inner::updateRow(RowIndex index) { + if (index.value >= 0) { + if (getRow(index)->disabled()) { + if (index == _selected.index) { + setSelected(Selected()); + } + if (index == _pressed.index) { + setPressed(Selected()); + } + } update(0, getRowTop(index), width(), _rowHeight); } } +template +bool PeerListBox::Inner::enumerateShownRows(Callback callback) { + return enumerateShownRows(0, shownRowsCount(), std::move(callback)); +} + +template +bool PeerListBox::Inner::enumerateShownRows(int from, int to, Callback callback) { + t_assert(0 <= from); + t_assert(from <= to); + if (showingSearch()) { + t_assert(to <= _filterResults.size()); + for (auto i = from; i != to; ++i) { + if (!callback(_filterResults[i])) { + return false; + } + } + } else { + t_assert(to <= _rows.size()); + for (auto i = from; i != to; ++i) { + if (!callback(_rows[i].get())) { + return false; + } + } + } + return true; +} + +PeerListBox::Row *PeerListBox::Inner::getRow(RowIndex index) { + 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; +} + +PeerListBox::Inner::RowIndex PeerListBox::Inner::findRowIndex(Row *row, RowIndex hint) { + if (!showingSearch()) { + 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; +} + void PeerListBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { if (auto row = findRow(peer)) { if (_searchable) { addToSearchIndex(row); } row->refreshName(); - update(0, st::membersMarginTop + row->index() * _rowHeight, width(), _rowHeight); + updateRow(row); } } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 1853924667..6e2ec43605 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -27,6 +27,7 @@ class RippleAnimation; class MultiSelect; template class WidgetSlideWrap; +class FlatLabel; } // namespace Ui class PeerListBox : public BoxContent { @@ -73,11 +74,11 @@ public: QString action() const; int actionWidth() const; - void setIndex(int index) { - _index = index; + void setAbsoluteIndex(int index) { + _absoluteIndex = index; } - int index() const { - return _index; + int absoluteIndex() const { + return _absoluteIndex; } bool disabled() const { return _disabled; @@ -107,7 +108,7 @@ public: QString _action; int _actionWidth = 0; bool _disabled = false; - int _index = -1; + int _absoluteIndex = -1; OrderedSet _nameFirstChars; }; @@ -147,10 +148,13 @@ public: Row *findRow(PeerData *peer); void updateRow(Row *row); void removeRow(Row *row); - int rowsCount() const; + int fullRowsCount() const; void setAboutText(const QString &aboutText); + void setAbout(object_ptr about); void refreshRows(); void setSearchable(bool searchable); + void setSearchNoResultsText(const QString &noResultsText); + void setSearchNoResults(object_ptr searchNoResults); // callback takes two iterators, like [](auto &begin, auto &end). template @@ -169,6 +173,7 @@ private: object_ptr> createMultiSelect(); int getTopScrollSkip() const; void updateScrollSkips(); + void searchQueryChanged(const QString &query); object_ptr> _select = { nullptr }; @@ -193,20 +198,29 @@ public: void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + void searchQueryChanged(QString query); + void submitted(); + // Interface for the controller. void appendRow(std::unique_ptr row); void prependRow(std::unique_ptr row); Row *findRow(PeerData *peer); - void updateRow(Row *row); + void updateRow(Row *row) { + updateRow(row, RowIndex()); + } void removeRow(Row *row); - int rowsCount() const; - void setAboutText(const QString &aboutText); + int fullRowsCount() const; + void setAbout(object_ptr about); void refreshRows(); void setSearchable(bool searchable); + void setSearchNoResults(object_ptr searchNoResults); template void reorderRows(ReorderCallback &&callback) { - callback(std::begin(_rows), std::end(_rows)); + callback(_rows.begin(), _rows.end()); + for (auto &searchEntity : _searchIndex) { + callback(searchEntity.second.begin(), searchEntity.second.end()); + } refreshIndices(); } @@ -228,39 +242,74 @@ protected: private: void refreshIndices(); - struct SelectedRow { - int index = -1; - bool action = false; + struct RowIndex { + RowIndex() = default; + explicit RowIndex(int value) : value(value) { + } + int value = -1; }; - friend inline bool operator==(SelectedRow a, SelectedRow b) { - return (a.index == b.index) && (a.action == b.action); + friend inline bool operator==(RowIndex a, RowIndex b) { + return (a.value == b.value); } - friend inline bool operator!=(SelectedRow a, SelectedRow b) { + friend inline bool operator!=(RowIndex a, RowIndex b) { return !(a == b); } - void setPressed(SelectedRow pressed); + struct Selected { + Selected() = default; + Selected(RowIndex index, bool action) : index(index), action(action) { + } + Selected(int index, bool action) : index(index), action(action) { + } + RowIndex index; + bool action = false; + }; + friend inline bool operator==(Selected a, Selected b) { + return (a.index == b.index) && (a.action == b.action); + } + friend inline bool operator!=(Selected a, Selected b) { + return !(a == b); + } + + void setSelected(Selected selected); + void setPressed(Selected pressed); + void restoreSelection(); void updateSelection(); void loadProfilePhotos(); void checkScrollForPreload(); - void updateRowWithIndex(int index); - int getRowTop(int index) const; + void updateRow(Row *row, RowIndex hint); + void updateRow(RowIndex row); + int getRowTop(RowIndex row) const; + Row *getRow(RowIndex element); + RowIndex findRowIndex(Row *row, RowIndex hint = RowIndex()); - void paintRow(Painter &p, TimeMs ms, int index); + void paintRow(Painter &p, TimeMs ms, RowIndex index); void addRowEntry(Row *row); void addToSearchIndex(Row *row); void removeFromSearchIndex(Row *row); + bool showingSearch() const { + return !_searchQuery.isEmpty(); + } + int shownRowsCount() const { + return showingSearch() ? _filterResults.size() : _rows.size(); + } + template + bool enumerateShownRows(Callback callback); + template + bool enumerateShownRows(int from, int to, Callback callback); + + int labelHeight() const; Controller *_controller = nullptr; int _rowHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; - SelectedRow _selected; - SelectedRow _pressed; + Selected _selected; + Selected _pressed; bool _mouseSelection = false; std::vector> _rows; @@ -268,10 +317,11 @@ private: bool _searchable = false; std::map> _searchIndex; + QString _searchQuery; + std::vector _filterResults; - int _aboutWidth = 0; - int _aboutHeight = 0; - Text _about; + object_ptr _about = { nullptr }; + object_ptr _searchNoResults = { nullptr }; QPoint _lastMousePosition; diff --git a/Telegram/SourceFiles/settings/settings_blocked_box_controller.cpp b/Telegram/SourceFiles/settings/settings_blocked_box_controller.cpp index de7ffc5c9a..5a99fb6523 100644 --- a/Telegram/SourceFiles/settings/settings_blocked_box_controller.cpp +++ b/Telegram/SourceFiles/settings/settings_blocked_box_controller.cpp @@ -120,6 +120,7 @@ void BlockedBoxController::handleBlockedEvent(UserData *user) { if (user->isBlocked()) { if (prependRow(user)) { view()->refreshRows(); + view()->onScrollToY(0); } } else if (auto row = view()->findRow(user)) { view()->removeRow(row); @@ -166,6 +167,7 @@ void BlockUserBoxController::prepare() { view()->setTitle(lang(lng_blocked_list_add_title)); view()->addButton(lang(lng_cancel), [this] { view()->closeBox(); }); view()->setSearchable(true); + view()->setSearchNoResultsText(lang(lng_blocked_list_not_found)); rebuildRows(); @@ -191,7 +193,7 @@ void BlockUserBoxController::prepare() { void BlockUserBoxController::rebuildRows() { auto ms = getms(); - auto wasEmpty = !view()->rowsCount(); + auto wasEmpty = !view()->fullRowsCount(); auto appendList = [this](auto chats) { auto count = 0; for_const (auto row, chats->all()) { @@ -220,7 +222,7 @@ void BlockUserBoxController::rebuildRows() { } void BlockUserBoxController::checkForEmptyRows() { - if (view()->rowsCount()) { + if (view()->fullRowsCount()) { view()->setAboutText(QString()); } else { auto &sessionData = AuthSession::Current().data();