tdesktop/Telegram/SourceFiles/boxes/peer_list_box.h

599 lines
18 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
#include "base/timer.h"
namespace Ui {
class RippleAnimation;
class RoundImageCheckbox;
class MultiSelect;
template <typename Widget>
class WidgetSlideWrap;
class FlatLabel;
} // namespace Ui
using PeerListRowId = uint64;
class PeerListRow {
public:
PeerListRow(gsl::not_null<PeerData*> peer);
PeerListRow(gsl::not_null<PeerData*> peer, PeerListRowId id);
void setDisabled(bool disabled) {
_disabled = disabled;
}
// Checked state is controlled by the box with multiselect,
// not by the row itself, so there is no setChecked() method.
// We can query the checked state from row, but before it is
// added to the box it is always false.
bool checked() const;
gsl::not_null<PeerData*> peer() const {
return _peer;
}
PeerListRowId id() const {
return _id;
}
void setCustomStatus(const QString &status);
void clearCustomStatus();
virtual ~PeerListRow();
// Box interface.
virtual bool needsVerifiedIcon() const {
return _peer->isVerified();
}
virtual QSize actionSize() const {
return QSize();
}
virtual QMargins actionMargins() const {
return QMargins();
}
virtual void addActionRipple(QPoint point, base::lambda<void()> updateCallback) {
}
virtual void stopLastActionRipple() {
}
virtual void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) {
}
void refreshName();
const Text &name() const {
return _name;
}
enum class StatusType {
Online,
LastSeen,
Custom,
};
void refreshStatus();
void setAbsoluteIndex(int index) {
_absoluteIndex = index;
}
int absoluteIndex() const {
return _absoluteIndex;
}
bool disabled() const {
return _disabled;
}
bool isSearchResult() const {
return _isSearchResult;
}
void setIsSearchResult(bool isSearchResult) {
_isSearchResult = isSearchResult;
}
enum class SetStyle {
Animated,
Fast,
};
template <typename UpdateCallback>
void setChecked(bool checked, SetStyle style, UpdateCallback callback) {
if (checked && !_checkbox) {
createCheckbox(std::move(callback));
}
setCheckedInternal(checked, style);
}
void invalidatePixmapsCache();
template <typename UpdateCallback>
void addRipple(QSize size, QPoint point, UpdateCallback updateCallback);
void stopLastRipple();
void paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth);
void paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth);
float64 checkedRatio();
void setNameFirstChars(const OrderedSet<QChar> &nameFirstChars) {
_nameFirstChars = nameFirstChars;
}
const OrderedSet<QChar> &nameFirstChars() const {
return _nameFirstChars;
}
virtual void lazyInitialize();
virtual void paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected);
protected:
bool isInitialized() const {
return _initialized;
}
private:
void createCheckbox(base::lambda<void()> updateCallback);
void setCheckedInternal(bool checked, SetStyle style);
void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const;
PeerListRowId _id = 0;
gsl::not_null<PeerData*> _peer;
std::unique_ptr<Ui::RippleAnimation> _ripple;
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
Text _name;
QString _status;
StatusType _statusType = StatusType::Online;
OrderedSet<QChar> _nameFirstChars;
int _absoluteIndex = -1;
bool _initialized : 1;
bool _disabled : 1;
bool _isSearchResult : 1;
};
enum class PeerListSearchMode {
None,
Local,
Complex,
};
class PeerListDelegate {
public:
virtual void peerListSetTitle(base::lambda<QString()> title) = 0;
virtual void peerListSetDescription(object_ptr<Ui::FlatLabel> description) = 0;
virtual void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) = 0;
virtual void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) = 0;
virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0;
virtual void peerListAppendRow(std::unique_ptr<PeerListRow> row) = 0;
virtual void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) = 0;
virtual void peerListAppendFoundRow(gsl::not_null<PeerListRow*> row) = 0;
virtual void peerListPrependRow(std::unique_ptr<PeerListRow> row) = 0;
virtual void peerListUpdateRow(gsl::not_null<PeerListRow*> row) = 0;
virtual bool peerListIsRowSelected(gsl::not_null<PeerData*> peer) = 0;
virtual void peerListRemoveRow(gsl::not_null<PeerListRow*> row) = 0;
virtual void peerListSetRowChecked(gsl::not_null<PeerListRow*> row, bool checked) = 0;
virtual gsl::not_null<PeerListRow*> peerListRowAt(int index) = 0;
virtual void peerListRefreshRows() = 0;
virtual void peerListScrollToTop() = 0;
virtual int peerListFullRowsCount() = 0;
virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0;
virtual void peerListSortRows(base::lambda<bool(PeerListRow &a, PeerListRow &b)> compare) = 0;
virtual void peerListPartitionRows(base::lambda<bool(PeerListRow &a)> border) = 0;
template <typename PeerDataRange>
void peerListAddSelectedRows(PeerDataRange &&range) {
for (auto peer : range) {
peerListAddSelectedRowInBunch(peer);
}
peerListFinishSelectedRowsBunch();
}
virtual std::vector<gsl::not_null<PeerData*>> peerListCollectSelectedRows() = 0;
virtual ~PeerListDelegate() = default;
private:
virtual void peerListAddSelectedRowInBunch(gsl::not_null<PeerData*> peer) = 0;
virtual void peerListFinishSelectedRowsBunch() = 0;
};
class PeerListSearchDelegate {
public:
virtual void peerListSearchAddRow(gsl::not_null<PeerData*> peer) = 0;
virtual void peerListSearchRefreshRows() = 0;
virtual ~PeerListSearchDelegate() = default;
};
class PeerListSearchController {
public:
virtual void searchQuery(const QString &query) = 0;
virtual bool isLoading() = 0;
virtual bool loadMoreRows() = 0;
virtual ~PeerListSearchController() = default;
void setDelegate(gsl::not_null<PeerListSearchDelegate*> delegate) {
_delegate = delegate;
}
protected:
gsl::not_null<PeerListSearchDelegate*> delegate() const {
return _delegate;
}
private:
PeerListSearchDelegate *_delegate = nullptr;
};
class PeerListController : public PeerListSearchDelegate {
public:
// Search works only with RowId == peer->id.
PeerListController(std::unique_ptr<PeerListSearchController> searchController = nullptr);
void setDelegate(gsl::not_null<PeerListDelegate*> delegate) {
_delegate = delegate;
prepare();
}
virtual void prepare() = 0;
virtual void rowClicked(gsl::not_null<PeerListRow*> row) = 0;
virtual void rowActionClicked(gsl::not_null<PeerListRow*> row) {
}
virtual void loadMoreRows() {
}
bool isSearchLoading() const {
return _searchController ? _searchController->isLoading() : false;
}
virtual std::unique_ptr<PeerListRow> createSearchRow(gsl::not_null<PeerData*> peer) {
return std::unique_ptr<PeerListRow>();
}
bool isRowSelected(gsl::not_null<PeerData*> peer) {
return delegate()->peerListIsRowSelected(peer);
}
virtual bool searchInLocal() {
return true;
}
void search(const QString &query);
void peerListSearchAddRow(gsl::not_null<PeerData*> peer) override;
void peerListSearchRefreshRows() override;
virtual ~PeerListController() = default;
protected:
gsl::not_null<PeerListDelegate*> delegate() const {
return _delegate;
}
PeerListSearchController *searchController() const {
return _searchController.get();
}
void setDescriptionText(const QString &text);
void setSearchLoadingText(const QString &text);
void setSearchNoResultsText(const QString &text);
void setDescription(object_ptr<Ui::FlatLabel> description) {
delegate()->peerListSetDescription(std::move(description));
}
void setSearchLoading(object_ptr<Ui::FlatLabel> loading) {
delegate()->peerListSetSearchLoading(std::move(loading));
}
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults) {
delegate()->peerListSetSearchNoResults(std::move(noResults));
}
private:
PeerListDelegate *_delegate = nullptr;
std::unique_ptr<PeerListSearchController> _searchController = nullptr;
};
class PeerListBox : public BoxContent, public PeerListDelegate {
class Inner;
public:
PeerListBox(QWidget*, std::unique_ptr<PeerListController> controller, base::lambda<void(PeerListBox*)> init);
void peerListSetTitle(base::lambda<QString()> title) override {
setTitle(std::move(title));
}
void peerListSetDescription(object_ptr<Ui::FlatLabel> description) override;
void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) override;
void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) override;
void peerListSetSearchMode(PeerListSearchMode mode) override;
void peerListAppendRow(std::unique_ptr<PeerListRow> row) override;
void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) override;
void peerListAppendFoundRow(gsl::not_null<PeerListRow*> row) override;
void peerListPrependRow(std::unique_ptr<PeerListRow> row) override;
void peerListUpdateRow(gsl::not_null<PeerListRow*> row) override;
void peerListRemoveRow(gsl::not_null<PeerListRow*> row) override;
void peerListSetRowChecked(gsl::not_null<PeerListRow*> row, bool checked) override;
gsl::not_null<PeerListRow*> peerListRowAt(int index) override;
bool peerListIsRowSelected(gsl::not_null<PeerData*> peer) override;
std::vector<gsl::not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListRefreshRows() override;
void peerListScrollToTop() override;
int peerListFullRowsCount() override;
PeerListRow *peerListFindRow(PeerListRowId id) override;
void peerListSortRows(base::lambda<bool(PeerListRow &a, PeerListRow &b)> compare) override;
void peerListPartitionRows(base::lambda<bool(PeerListRow &a)> border) override;
protected:
void prepare() override;
void setInnerFocus() override;
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void peerListAddSelectedRowInBunch(gsl::not_null<PeerData*> peer) override {
addSelectItem(peer, PeerListRow::SetStyle::Fast);
}
void peerListFinishSelectedRowsBunch() override;
void addSelectItem(gsl::not_null<PeerData*> peer, PeerListRow::SetStyle style);
object_ptr<Ui::WidgetSlideWrap<Ui::MultiSelect>> createMultiSelect();
int getTopScrollSkip() const;
void updateScrollSkips();
void searchQueryChanged(const QString &query);
object_ptr<Ui::WidgetSlideWrap<Ui::MultiSelect>> _select = { nullptr };
QPointer<Inner> _inner;
std::unique_ptr<PeerListController> _controller;
base::lambda<void(PeerListBox*)> _init;
};
// This class is hold in header because it requires Qt preprocessing.
class PeerListBox::Inner : public TWidget, private base::Subscriber {
Q_OBJECT
public:
Inner(QWidget *parent, gsl::not_null<PeerListController*> controller);
void selectSkip(int direction);
void selectSkipPage(int height, int direction);
void clearSelection();
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
void searchQueryChanged(QString query);
void submitted();
// Interface for the controller.
void appendRow(std::unique_ptr<PeerListRow> row);
void appendSearchRow(std::unique_ptr<PeerListRow> row);
void appendFoundRow(gsl::not_null<PeerListRow*> row);
void prependRow(std::unique_ptr<PeerListRow> row);
PeerListRow *findRow(PeerListRowId id);
void updateRow(gsl::not_null<PeerListRow*> row) {
updateRow(row, RowIndex());
}
void removeRow(gsl::not_null<PeerListRow*> row);
int fullRowsCount() const;
gsl::not_null<PeerListRow*> rowAt(int index) const;
void setDescription(object_ptr<Ui::FlatLabel> description);
void setSearchLoading(object_ptr<Ui::FlatLabel> loading);
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults);
void refreshRows();
void setSearchMode(PeerListSearchMode mode);
void changeCheckState(gsl::not_null<PeerListRow*> row, bool checked, PeerListRow::SetStyle style);
template <typename ReorderCallback>
void reorderRows(ReorderCallback &&callback) {
callback(_rows.begin(), _rows.end());
for (auto &searchEntity : _searchIndex) {
callback(searchEntity.second.begin(), searchEntity.second.end());
}
refreshIndices();
}
signals:
void mustScrollTo(int ymin, int ymax);
public slots:
void peerUpdated(PeerData *peer);
void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
protected:
void paintEvent(QPaintEvent *e) override;
void enterEventHook(QEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
void refreshIndices();
void invalidatePixmapsCache();
struct RowIndex {
RowIndex() {
}
explicit RowIndex(int value) : value(value) {
}
int value = -1;
};
friend inline bool operator==(RowIndex a, RowIndex b) {
return (a.value == b.value);
}
friend inline bool operator!=(RowIndex a, RowIndex b) {
return !(a == b);
}
struct Selected {
Selected() {
}
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 updateRow(gsl::not_null<PeerListRow*> row, RowIndex hint);
void updateRow(RowIndex row);
int getRowTop(RowIndex row) const;
PeerListRow *getRow(RowIndex element);
RowIndex findRowIndex(gsl::not_null<PeerListRow*> row, RowIndex hint = RowIndex());
QRect getActionRect(gsl::not_null<PeerListRow*> row, RowIndex index) const;
void paintRow(Painter &p, TimeMs ms, RowIndex index);
void addRowEntry(gsl::not_null<PeerListRow*> row);
void addToSearchIndex(gsl::not_null<PeerListRow*> row);
bool addingToSearchIndex() const;
void removeFromSearchIndex(gsl::not_null<PeerListRow*> row);
void setSearchQuery(const QString &query, const QString &normalizedQuery);
bool showingSearch() const {
return !_searchQuery.isEmpty();
}
int shownRowsCount() const {
return showingSearch() ? _filterResults.size() : _rows.size();
}
template <typename Callback>
bool enumerateShownRows(Callback callback);
template <typename Callback>
bool enumerateShownRows(int from, int to, Callback callback);
int labelHeight() const;
void clearSearchRows();
gsl::not_null<PeerListController*> _controller;
PeerListSearchMode _searchMode = PeerListSearchMode::None;
int _rowHeight = 0;
int _visibleTop = 0;
int _visibleBottom = 0;
Selected _selected;
Selected _pressed;
bool _mouseSelection = false;
std::vector<std::unique_ptr<PeerListRow>> _rows;
std::map<PeerListRowId, gsl::not_null<PeerListRow*>> _rowsById;
std::map<PeerData*, std::vector<gsl::not_null<PeerListRow*>>> _rowsByPeer;
std::map<QChar, std::vector<gsl::not_null<PeerListRow*>>> _searchIndex;
QString _searchQuery;
QString _normalizedSearchQuery;
QString _mentionHighlight;
std::vector<gsl::not_null<PeerListRow*>> _filterResults;
object_ptr<Ui::FlatLabel> _description = { nullptr };
object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
QPoint _lastMousePosition;
std::vector<std::unique_ptr<PeerListRow>> _searchRows;
};
class PeerListRowWithLink : public PeerListRow {
public:
using PeerListRow::PeerListRow;
void setActionLink(const QString &action);
void lazyInitialize() override;
private:
void refreshActionLink();
QSize actionSize() const override;
QMargins actionMargins() const override;
void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) override;
QString _action;
int _actionWidth = 0;
};
class PeerListGlobalSearchController : public PeerListSearchController, private MTP::Sender {
public:
PeerListGlobalSearchController();
void searchQuery(const QString &query) override;
bool isLoading() override;
bool loadMoreRows() override {
return false;
}
private:
bool searchInCache();
void searchOnServer();
void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId);
base::Timer _timer;
QString _query;
mtpRequestId _requestId = 0;
std::map<QString, MTPcontacts_Found> _cache;
std::map<mtpRequestId, QString> _queries;
};
class ChatsListBoxController : public PeerListController, protected base::Subscriber {
public:
ChatsListBoxController(std::unique_ptr<PeerListSearchController> searchController = std::make_unique<PeerListGlobalSearchController>());
void prepare() override final;
std::unique_ptr<PeerListRow> createSearchRow(gsl::not_null<PeerData*> peer) override final;
protected:
class Row : public PeerListRow {
public:
Row(gsl::not_null<History*> history) : PeerListRow(history->peer), _history(history) {
}
gsl::not_null<History*> history() const {
return _history;
}
private:
gsl::not_null<History*> _history;
};
virtual std::unique_ptr<Row> createRow(gsl::not_null<History*> history) = 0;
virtual void prepareViewHook() = 0;
virtual void updateRowHook(Row *row) {
}
private:
void rebuildRows();
void checkForEmptyRows();
bool appendRow(History *history);
};