1116 lines
31 KiB
C++
1116 lines
31 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#pragma once
|
|
|
|
#include "ui/rp_widget.h"
|
|
#include "ui/empty_userpic.h"
|
|
#include "ui/unread_badge.h"
|
|
#include "ui/userpic_view.h"
|
|
#include "ui/layers/box_content.h"
|
|
#include "base/timer.h"
|
|
|
|
namespace style {
|
|
struct PeerList;
|
|
struct PeerListItem;
|
|
struct MultiSelect;
|
|
} // namespace style
|
|
|
|
namespace Main {
|
|
class Session;
|
|
class SessionShow;
|
|
} // namespace Main
|
|
|
|
namespace Ui {
|
|
class RippleAnimation;
|
|
class RoundImageCheckbox;
|
|
class MultiSelect;
|
|
template <typename Widget>
|
|
class SlideWrap;
|
|
class FlatLabel;
|
|
struct ScrollToRequest;
|
|
class PopupMenu;
|
|
struct OutlineSegment;
|
|
} // namespace Ui
|
|
|
|
using PaintRoundImageCallback = Fn<void(
|
|
Painter &p,
|
|
int x,
|
|
int y,
|
|
int outerWidth,
|
|
int size)>;
|
|
|
|
[[nodiscard]] PaintRoundImageCallback PaintUserpicCallback(
|
|
not_null<PeerData*> peer,
|
|
bool respectSavedMessagesChat);
|
|
[[nodiscard]] PaintRoundImageCallback ForceRoundUserpicCallback(
|
|
not_null<PeerData*> peer);
|
|
|
|
using PeerListRowId = uint64;
|
|
|
|
[[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d);
|
|
|
|
class PeerListRow {
|
|
public:
|
|
enum class State {
|
|
Active,
|
|
Disabled,
|
|
DisabledChecked,
|
|
};
|
|
|
|
explicit PeerListRow(not_null<PeerData*> peer);
|
|
PeerListRow(not_null<PeerData*> peer, PeerListRowId id);
|
|
|
|
virtual ~PeerListRow();
|
|
|
|
void setDisabledState(State state) {
|
|
_disabledState = state;
|
|
}
|
|
|
|
// 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.
|
|
[[nodiscard]] bool checked() const;
|
|
|
|
[[nodiscard]] bool special() const {
|
|
return !_peer;
|
|
}
|
|
[[nodiscard]] not_null<PeerData*> peer() const {
|
|
Expects(!special());
|
|
|
|
return _peer;
|
|
}
|
|
[[nodiscard]] PeerListRowId id() const {
|
|
return _id;
|
|
}
|
|
|
|
[[nodiscard]] Ui::PeerUserpicView &ensureUserpicView();
|
|
|
|
[[nodiscard]] virtual QString generateName();
|
|
[[nodiscard]] virtual QString generateShortName();
|
|
[[nodiscard]] virtual auto generatePaintUserpicCallback(
|
|
bool forceRound) -> PaintRoundImageCallback;
|
|
|
|
[[nodiscard]] virtual auto generateNameFirstLetters() const
|
|
-> const base::flat_set<QChar> &;
|
|
[[nodiscard]] virtual auto generateNameWords() const
|
|
-> const base::flat_set<QString> &;
|
|
|
|
virtual void preloadUserpic();
|
|
|
|
void setCustomStatus(const QString &status, bool active = false);
|
|
void clearCustomStatus();
|
|
|
|
// Box interface.
|
|
virtual int paintNameIconGetWidth(
|
|
Painter &p,
|
|
Fn<void()> repaint,
|
|
crl::time now,
|
|
int nameLeft,
|
|
int nameTop,
|
|
int nameWidth,
|
|
int availableWidth,
|
|
int outerWidth,
|
|
bool selected);
|
|
|
|
virtual QSize rightActionSize() const {
|
|
return QSize();
|
|
}
|
|
virtual QMargins rightActionMargins() const {
|
|
return QMargins();
|
|
}
|
|
virtual bool rightActionDisabled() const {
|
|
return false;
|
|
}
|
|
virtual void rightActionPaint(
|
|
Painter &p,
|
|
int x,
|
|
int y,
|
|
int outerWidth,
|
|
bool selected,
|
|
bool actionSelected) {
|
|
}
|
|
virtual void rightActionAddRipple(
|
|
QPoint point,
|
|
Fn<void()> updateCallback) {
|
|
}
|
|
virtual void rightActionStopLastRipple() {
|
|
}
|
|
[[nodiscard]] virtual float64 opacity() {
|
|
return 1.;
|
|
}
|
|
|
|
// By default elements code falls back to a simple right action code.
|
|
virtual int elementsCount() const;
|
|
virtual QRect elementGeometry(int element, int outerWidth) const;
|
|
virtual bool elementDisabled(int element) const;
|
|
virtual bool elementOnlySelect(int element) const;
|
|
virtual void elementAddRipple(
|
|
int element,
|
|
QPoint point,
|
|
Fn<void()> updateCallback);
|
|
virtual void elementsStopLastRipple();
|
|
virtual void elementsPaint(
|
|
Painter &p,
|
|
int outerWidth,
|
|
bool selected,
|
|
int selectedElement);
|
|
|
|
virtual void refreshName(const style::PeerListItem &st);
|
|
const Ui::Text::String &name() const {
|
|
return _name;
|
|
}
|
|
|
|
virtual bool useForumLikeUserpic() const;
|
|
|
|
enum class StatusType {
|
|
Online,
|
|
LastSeen,
|
|
Custom,
|
|
CustomActive,
|
|
};
|
|
virtual void refreshStatus();
|
|
crl::time refreshStatusTime() const;
|
|
|
|
void setAbsoluteIndex(int index) {
|
|
_absoluteIndex = index;
|
|
}
|
|
int absoluteIndex() const {
|
|
return _absoluteIndex;
|
|
}
|
|
bool disabled() const {
|
|
return (_disabledState != State::Active);
|
|
}
|
|
bool isSearchResult() const {
|
|
return _isSearchResult;
|
|
}
|
|
void setIsSearchResult(bool isSearchResult) {
|
|
_isSearchResult = isSearchResult;
|
|
}
|
|
void setSavedMessagesChatStatus(QString savedMessagesStatus) {
|
|
_savedMessagesStatus = savedMessagesStatus;
|
|
}
|
|
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
|
|
_isRepliesMessagesChat = isRepliesMessagesChat;
|
|
}
|
|
|
|
template <typename UpdateCallback>
|
|
void setChecked(
|
|
bool checked,
|
|
const style::RoundImageCheckbox &st,
|
|
anim::type animated,
|
|
UpdateCallback callback) {
|
|
if (checked && !_checkbox) {
|
|
createCheckbox(st, std::move(callback));
|
|
}
|
|
setCheckedInternal(checked, animated);
|
|
}
|
|
void setCustomizedCheckSegments(
|
|
std::vector<Ui::OutlineSegment> segments);
|
|
void setHidden(bool hidden) {
|
|
_hidden = hidden;
|
|
}
|
|
[[nodiscard]] bool hidden() const {
|
|
return _hidden;
|
|
}
|
|
void finishCheckedAnimation();
|
|
void invalidatePixmapsCache();
|
|
|
|
template <typename MaskGenerator, typename UpdateCallback>
|
|
void addRipple(
|
|
const style::PeerListItem &st,
|
|
MaskGenerator &&maskGenerator,
|
|
QPoint point,
|
|
UpdateCallback &&updateCallback);
|
|
void stopLastRipple();
|
|
void paintRipple(Painter &p, int x, int y, int outerWidth);
|
|
void paintUserpic(
|
|
Painter &p,
|
|
const style::PeerListItem &st,
|
|
int x,
|
|
int y,
|
|
int outerWidth);
|
|
float64 checkedRatio();
|
|
|
|
void setNameFirstLetters(const base::flat_set<QChar> &firstLetters) {
|
|
_nameFirstLetters = firstLetters;
|
|
}
|
|
const base::flat_set<QChar> &nameFirstLetters() const {
|
|
return _nameFirstLetters;
|
|
}
|
|
|
|
virtual void lazyInitialize(const style::PeerListItem &st);
|
|
virtual void paintStatusText(
|
|
Painter &p,
|
|
const style::PeerListItem &st,
|
|
int x,
|
|
int y,
|
|
int availableWidth,
|
|
int outerWidth,
|
|
bool selected);
|
|
|
|
protected:
|
|
bool isInitialized() const {
|
|
return _initialized;
|
|
}
|
|
|
|
explicit PeerListRow(PeerListRowId id);
|
|
|
|
private:
|
|
void createCheckbox(
|
|
const style::RoundImageCheckbox &st,
|
|
Fn<void()> updateCallback);
|
|
void setCheckedInternal(bool checked, anim::type animated);
|
|
void paintDisabledCheckUserpic(
|
|
Painter &p,
|
|
const style::PeerListItem &st,
|
|
int x,
|
|
int y,
|
|
int outerWidth) const;
|
|
void setStatusText(const QString &text);
|
|
|
|
PeerListRowId _id = 0;
|
|
PeerData *_peer = nullptr;
|
|
mutable Ui::PeerUserpicView _userpic;
|
|
std::unique_ptr<Ui::RippleAnimation> _ripple;
|
|
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
|
|
Ui::Text::String _name;
|
|
Ui::Text::String _status;
|
|
Ui::PeerBadge _bagde;
|
|
StatusType _statusType = StatusType::Online;
|
|
crl::time _statusValidTill = 0;
|
|
base::flat_set<QChar> _nameFirstLetters;
|
|
QString _savedMessagesStatus;
|
|
int _absoluteIndex = -1;
|
|
State _disabledState = State::Active;
|
|
bool _hidden : 1 = false;
|
|
bool _initialized : 1 = false;
|
|
bool _isSearchResult : 1 = false;
|
|
bool _isRepliesMessagesChat : 1 = false;
|
|
|
|
};
|
|
|
|
enum class PeerListSearchMode {
|
|
Disabled,
|
|
Enabled,
|
|
};
|
|
|
|
struct PeerListState;
|
|
|
|
class PeerListDelegate {
|
|
public:
|
|
virtual void peerListSetTitle(rpl::producer<QString> title) = 0;
|
|
virtual void peerListSetAdditionalTitle(rpl::producer<QString> title) = 0;
|
|
virtual void peerListSetHideEmpty(bool hide) = 0;
|
|
virtual void peerListSetDescription(object_ptr<Ui::FlatLabel> description) = 0;
|
|
virtual void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) = 0;
|
|
virtual void peerListSetAboveWidget(object_ptr<Ui::RpWidget> aboveWidget) = 0;
|
|
virtual void peerListSetAboveSearchWidget(object_ptr<Ui::RpWidget> aboveWidget) = 0;
|
|
virtual void peerListSetBelowWidget(object_ptr<Ui::RpWidget> belowWidget) = 0;
|
|
virtual void peerListMouseLeftGeometry() = 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(not_null<PeerListRow*> row) = 0;
|
|
virtual void peerListPrependRow(std::unique_ptr<PeerListRow> row) = 0;
|
|
virtual void peerListPrependRowFromSearchResult(not_null<PeerListRow*> row) = 0;
|
|
virtual void peerListUpdateRow(not_null<PeerListRow*> row) = 0;
|
|
virtual void peerListRemoveRow(not_null<PeerListRow*> row) = 0;
|
|
virtual void peerListConvertRowToSearchResult(not_null<PeerListRow*> row) = 0;
|
|
virtual bool peerListIsRowChecked(not_null<PeerListRow*> row) = 0;
|
|
virtual void peerListSetRowChecked(not_null<PeerListRow*> row, bool checked) = 0;
|
|
virtual void peerListSetRowHidden(not_null<PeerListRow*> row, bool hidden) = 0;
|
|
virtual void peerListSetForeignRowChecked(
|
|
not_null<PeerListRow*> row,
|
|
bool checked,
|
|
anim::type animated) = 0;
|
|
virtual 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 int peerListSearchRowsCount() = 0;
|
|
virtual not_null<PeerListRow*> peerListSearchRowAt(int index) = 0;
|
|
virtual std::optional<QPoint> peerListLastRowMousePosition() = 0;
|
|
virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
|
|
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
|
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
|
|
|
|
template <typename PeerDataRange>
|
|
void peerListAddSelectedPeers(PeerDataRange &&range) {
|
|
for (const auto peer : range) {
|
|
peerListAddSelectedPeerInBunch(peer);
|
|
}
|
|
peerListFinishSelectedRowsBunch();
|
|
}
|
|
|
|
template <typename PeerListRowRange>
|
|
void peerListAddSelectedRows(PeerListRowRange &&range) {
|
|
for (const auto row : range) {
|
|
peerListAddSelectedRowInBunch(row);
|
|
}
|
|
peerListFinishSelectedRowsBunch();
|
|
}
|
|
|
|
virtual void peerListShowRowMenu(
|
|
not_null<PeerListRow*> row,
|
|
bool highlightRow,
|
|
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) = 0;
|
|
virtual int peerListSelectedRowsCount() = 0;
|
|
virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
|
|
virtual void peerListRestoreState(
|
|
std::unique_ptr<PeerListState> state) = 0;
|
|
virtual ~PeerListDelegate() = default;
|
|
|
|
private:
|
|
virtual void peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) = 0;
|
|
virtual void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) = 0;
|
|
virtual void peerListFinishSelectedRowsBunch() = 0;
|
|
|
|
};
|
|
|
|
class PeerListSearchDelegate {
|
|
public:
|
|
virtual void peerListSearchAddRow(not_null<PeerData*> peer) = 0;
|
|
virtual void peerListSearchAddRow(PeerListRowId id) = 0;
|
|
virtual void peerListSearchRefreshRows() = 0;
|
|
virtual ~PeerListSearchDelegate() = default;
|
|
|
|
};
|
|
|
|
class PeerListSearchController {
|
|
public:
|
|
struct SavedStateBase {
|
|
virtual ~SavedStateBase() = default;
|
|
};
|
|
|
|
virtual void searchQuery(const QString &query) = 0;
|
|
virtual bool isLoading() = 0;
|
|
virtual bool loadMoreRows() = 0;
|
|
virtual ~PeerListSearchController() = default;
|
|
|
|
void setDelegate(not_null<PeerListSearchDelegate*> delegate) {
|
|
_delegate = delegate;
|
|
}
|
|
|
|
virtual std::unique_ptr<SavedStateBase> saveState() const {
|
|
return nullptr;
|
|
}
|
|
virtual void restoreState(
|
|
std::unique_ptr<SavedStateBase> state) {
|
|
}
|
|
|
|
rpl::lifetime &lifetime() {
|
|
return _lifetime;
|
|
}
|
|
|
|
protected:
|
|
not_null<PeerListSearchDelegate*> delegate() const {
|
|
return _delegate;
|
|
}
|
|
|
|
private:
|
|
PeerListSearchDelegate *_delegate = nullptr;
|
|
rpl::lifetime _lifetime;
|
|
|
|
};
|
|
|
|
class PeerListController : public PeerListSearchDelegate {
|
|
public:
|
|
struct SavedStateBase {
|
|
virtual ~SavedStateBase() = default;
|
|
};
|
|
|
|
// Search works only with RowId == peer->id.
|
|
PeerListController(
|
|
std::unique_ptr<PeerListSearchController> searchController = {});
|
|
|
|
void setDelegate(not_null<PeerListDelegate*> delegate) {
|
|
_delegate = delegate;
|
|
prepare();
|
|
}
|
|
[[nodiscard]] not_null<PeerListDelegate*> delegate() const {
|
|
return _delegate;
|
|
}
|
|
|
|
void setStyleOverrides(
|
|
const style::PeerList *listSt,
|
|
const style::MultiSelect *selectSt = nullptr) {
|
|
_listSt = listSt;
|
|
_selectSt = selectSt;
|
|
}
|
|
const style::PeerList *listSt() const {
|
|
return _listSt;
|
|
}
|
|
const style::MultiSelect *selectSt() const {
|
|
return _selectSt;
|
|
}
|
|
const style::PeerList &computeListSt() const;
|
|
const style::MultiSelect &computeSelectSt() const;
|
|
|
|
virtual Main::Session &session() const = 0;
|
|
|
|
virtual void prepare() = 0;
|
|
|
|
virtual void showFinished() {
|
|
}
|
|
|
|
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
|
|
virtual void rowRightActionClicked(not_null<PeerListRow*> row) {
|
|
}
|
|
|
|
// By default elements code falls back to a simple right action code.
|
|
virtual void rowElementClicked(not_null<PeerListRow*> row, int element) {
|
|
if (element == 1) {
|
|
rowRightActionClicked(row);
|
|
}
|
|
}
|
|
|
|
virtual void loadMoreRows() {
|
|
}
|
|
virtual void itemDeselectedHook(not_null<PeerData*> peer) {
|
|
}
|
|
virtual bool isForeignRow(PeerListRowId itemId) {
|
|
return false;
|
|
}
|
|
virtual bool handleDeselectForeignRow(PeerListRowId itemId) {
|
|
return false;
|
|
}
|
|
virtual base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
|
QWidget *parent,
|
|
not_null<PeerListRow*> row);
|
|
bool isSearchLoading() const {
|
|
return _searchController ? _searchController->isLoading() : false;
|
|
}
|
|
virtual std::unique_ptr<PeerListRow> createSearchRow(
|
|
not_null<PeerData*> peer) {
|
|
return nullptr;
|
|
}
|
|
virtual std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id);
|
|
virtual std::unique_ptr<PeerListRow> createRestoredRow(
|
|
not_null<PeerData*> peer) {
|
|
return nullptr;
|
|
}
|
|
|
|
virtual std::unique_ptr<PeerListState> saveState() const;
|
|
virtual void restoreState(
|
|
std::unique_ptr<PeerListState> state);
|
|
|
|
[[nodiscard]] virtual int contentWidth() const;
|
|
[[nodiscard]] virtual rpl::producer<int> boxHeightValue() const;
|
|
[[nodiscard]] virtual int descriptionTopSkipMin() const;
|
|
|
|
[[nodiscard]] bool isRowSelected(not_null<PeerListRow*> row) {
|
|
return delegate()->peerListIsRowChecked(row);
|
|
}
|
|
|
|
virtual bool trackSelectedList() {
|
|
return true;
|
|
}
|
|
virtual bool searchInLocal() {
|
|
return true;
|
|
}
|
|
[[nodiscard]] bool hasComplexSearch() const;
|
|
void search(const QString &query);
|
|
|
|
void peerListSearchAddRow(not_null<PeerData*> peer) override;
|
|
void peerListSearchAddRow(PeerListRowId id) override;
|
|
void peerListSearchRefreshRows() override;
|
|
|
|
[[nodiscard]] virtual QString savedMessagesChatStatus() const {
|
|
return QString();
|
|
}
|
|
[[nodiscard]] virtual int customRowHeight() {
|
|
Unexpected("PeerListController::customRowHeight.");
|
|
}
|
|
virtual void customRowPaint(
|
|
Painter &p,
|
|
crl::time now,
|
|
not_null<PeerListRow*> row,
|
|
bool selected) {
|
|
Unexpected("PeerListController::customRowPaint.");
|
|
}
|
|
[[nodiscard]] virtual bool customRowSelectionPoint(
|
|
not_null<PeerListRow*> row,
|
|
int x,
|
|
int y) {
|
|
Unexpected("PeerListController::customRowSelectionPoint.");
|
|
}
|
|
[[nodiscard]] virtual Fn<QImage()> customRowRippleMaskGenerator() {
|
|
Unexpected("PeerListController::customRowRippleMaskGenerator.");
|
|
}
|
|
|
|
[[nodiscard]] rpl::lifetime &lifetime() {
|
|
return _lifetime;
|
|
}
|
|
|
|
virtual ~PeerListController() = default;
|
|
|
|
protected:
|
|
PeerListSearchController *searchController() const {
|
|
return _searchController.get();
|
|
}
|
|
|
|
void setDescriptionText(const QString &text);
|
|
void setSearchNoResultsText(const QString &text);
|
|
void setDescription(object_ptr<Ui::FlatLabel> description) {
|
|
delegate()->peerListSetDescription(std::move(description));
|
|
}
|
|
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults) {
|
|
delegate()->peerListSetSearchNoResults(std::move(noResults));
|
|
}
|
|
|
|
void sortByName();
|
|
|
|
private:
|
|
PeerListDelegate *_delegate = nullptr;
|
|
std::unique_ptr<PeerListSearchController> _searchController = nullptr;
|
|
|
|
const style::PeerList *_listSt = nullptr;
|
|
const style::MultiSelect *_selectSt = nullptr;
|
|
|
|
rpl::lifetime _lifetime;
|
|
|
|
};
|
|
|
|
struct PeerListState {
|
|
PeerListState() = default;
|
|
PeerListState(PeerListState &&other) = delete;
|
|
PeerListState &operator=(PeerListState &&other) = delete;
|
|
|
|
std::unique_ptr<PeerListController::SavedStateBase> controllerState;
|
|
std::vector<not_null<PeerData*>> list;
|
|
std::vector<not_null<PeerData*>> filterResults;
|
|
QString searchQuery;
|
|
};
|
|
|
|
class PeerListContent : public Ui::RpWidget {
|
|
public:
|
|
PeerListContent(
|
|
QWidget *parent,
|
|
not_null<PeerListController*> controller);
|
|
|
|
struct SkipResult {
|
|
int shouldMoveTo = 0;
|
|
int reallyMovedTo = 0;
|
|
};
|
|
SkipResult selectSkip(int direction);
|
|
void selectSkipPage(int height, int direction);
|
|
|
|
enum class Mode {
|
|
Default,
|
|
Custom,
|
|
};
|
|
void setMode(Mode mode);
|
|
|
|
[[nodiscard]] rpl::producer<int> selectedIndexValue() const;
|
|
[[nodiscard]] bool hasSelection() const;
|
|
[[nodiscard]] bool hasPressed() const;
|
|
void clearSelection();
|
|
|
|
void searchQueryChanged(QString query);
|
|
bool submitted();
|
|
|
|
// Interface for the controller.
|
|
void appendRow(std::unique_ptr<PeerListRow> row);
|
|
void appendSearchRow(std::unique_ptr<PeerListRow> row);
|
|
void appendFoundRow(not_null<PeerListRow*> row);
|
|
void prependRow(std::unique_ptr<PeerListRow> row);
|
|
void prependRowFromSearchResult(not_null<PeerListRow*> row);
|
|
PeerListRow *findRow(PeerListRowId id);
|
|
std::optional<QPoint> lastRowMousePosition() const;
|
|
void updateRow(not_null<PeerListRow*> row) {
|
|
updateRow(row, RowIndex());
|
|
}
|
|
void removeRow(not_null<PeerListRow*> row);
|
|
void convertRowToSearchResult(not_null<PeerListRow*> row);
|
|
int fullRowsCount() const;
|
|
not_null<PeerListRow*> rowAt(int index) const;
|
|
int searchRowsCount() const;
|
|
not_null<PeerListRow*> searchRowAt(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 setAboveWidget(object_ptr<Ui::RpWidget> widget);
|
|
void setAboveSearchWidget(object_ptr<Ui::RpWidget> widget);
|
|
void setBelowWidget(object_ptr<Ui::RpWidget> width);
|
|
void setHideEmpty(bool hide);
|
|
void refreshRows();
|
|
|
|
void mouseLeftGeometry();
|
|
|
|
void setSearchMode(PeerListSearchMode mode);
|
|
void changeCheckState(
|
|
not_null<PeerListRow*> row,
|
|
bool checked,
|
|
anim::type animated);
|
|
void setRowHidden(
|
|
not_null<PeerListRow*> row,
|
|
bool hidden);
|
|
|
|
template <typename ReorderCallback>
|
|
void reorderRows(ReorderCallback &&callback) {
|
|
callback(_rows.begin(), _rows.end());
|
|
for (auto &searchEntity : _searchIndex) {
|
|
callback(searchEntity.second.begin(), searchEntity.second.end());
|
|
}
|
|
refreshIndices();
|
|
if (!_hiddenRows.empty()) {
|
|
callback(_filterResults.begin(), _filterResults.end());
|
|
}
|
|
update();
|
|
}
|
|
|
|
std::unique_ptr<PeerListState> saveState() const;
|
|
void restoreState(std::unique_ptr<PeerListState> state);
|
|
|
|
void showRowMenu(
|
|
not_null<PeerListRow*> row,
|
|
bool highlightRow,
|
|
Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
|
|
|
|
auto scrollToRequests() const {
|
|
return _scrollToRequests.events();
|
|
}
|
|
|
|
~PeerListContent();
|
|
|
|
protected:
|
|
int resizeGetHeight(int newWidth) override;
|
|
void visibleTopBottomUpdated(
|
|
int visibleTop,
|
|
int visibleBottom) override;
|
|
|
|
void paintEvent(QPaintEvent *e) override;
|
|
void enterEventHook(QEnterEvent *e) override;
|
|
void leaveEventHook(QEvent *e) override;
|
|
void mouseMoveEvent(QMouseEvent *e) override;
|
|
void mousePressEvent(QMouseEvent *e) override;
|
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
|
void contextMenuEvent(QContextMenuEvent *e) override;
|
|
|
|
private:
|
|
void refreshIndices();
|
|
void removeRowAtIndex(std::vector<std::unique_ptr<PeerListRow>> &from, int index);
|
|
void handleNameChanged(not_null<PeerData*> peer);
|
|
|
|
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, int element)
|
|
: index(index)
|
|
, element(element) {
|
|
}
|
|
Selected(int index, int element)
|
|
: index(index)
|
|
, element(element) {
|
|
}
|
|
|
|
RowIndex index;
|
|
int element = 0;
|
|
};
|
|
friend inline bool operator==(Selected a, Selected b) {
|
|
return (a.index == b.index) && (a.element == b.element);
|
|
}
|
|
friend inline bool operator!=(Selected a, Selected b) {
|
|
return !(a == b);
|
|
}
|
|
struct SelectedSaved {
|
|
SelectedSaved(PeerListRowId id, Selected old)
|
|
: id(id), old(old) {
|
|
}
|
|
PeerListRowId id = 0;
|
|
Selected old;
|
|
};
|
|
|
|
void setSelected(Selected selected);
|
|
void setPressed(Selected pressed);
|
|
void setContexted(Selected contexted);
|
|
void restoreSelection();
|
|
SelectedSaved saveSelectedData(Selected from);
|
|
Selected restoreSelectedData(SelectedSaved from);
|
|
|
|
void selectByMouse(QPoint globalPosition);
|
|
void loadProfilePhotos();
|
|
void checkScrollForPreload();
|
|
|
|
void updateRow(not_null<PeerListRow*> row, RowIndex hint);
|
|
void updateRow(RowIndex row);
|
|
int getRowTop(RowIndex row) const;
|
|
PeerListRow *getRow(RowIndex element);
|
|
RowIndex findRowIndex(
|
|
not_null<PeerListRow*> row,
|
|
RowIndex hint = RowIndex());
|
|
QRect getElementRect(
|
|
not_null<PeerListRow*> row,
|
|
RowIndex index,
|
|
int element) const;
|
|
|
|
bool showRowMenu(
|
|
RowIndex index,
|
|
PeerListRow *row,
|
|
QPoint globalPos,
|
|
bool highlightRow,
|
|
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr);
|
|
|
|
crl::time paintRow(Painter &p, crl::time now, RowIndex index);
|
|
|
|
void addRowEntry(not_null<PeerListRow*> row);
|
|
void addToSearchIndex(not_null<PeerListRow*> row);
|
|
bool addingToSearchIndex() const;
|
|
void removeFromSearchIndex(not_null<PeerListRow*> row);
|
|
void setSearchQuery(const QString &query, const QString &normalizedQuery);
|
|
bool showingSearch() const {
|
|
return !_hiddenRows.empty() || !_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 rowsTop() const;
|
|
int labelHeight() const;
|
|
|
|
void clearSearchRows();
|
|
void clearAllContent();
|
|
void handleMouseMove(QPoint globalPosition);
|
|
void mousePressReleased(Qt::MouseButton button);
|
|
void initDecorateWidget(Ui::RpWidget *widget);
|
|
|
|
const style::PeerList &_st;
|
|
not_null<PeerListController*> _controller;
|
|
PeerListSearchMode _searchMode = PeerListSearchMode::Disabled;
|
|
|
|
Mode _mode = Mode::Default;
|
|
int _rowHeight = 0;
|
|
int _visibleTop = 0;
|
|
int _visibleBottom = 0;
|
|
|
|
Selected _selected;
|
|
Selected _pressed;
|
|
Selected _contexted;
|
|
rpl::variable<int> _selectedIndex = -1;
|
|
bool _mouseSelection = false;
|
|
std::optional<QPoint> _lastMousePosition;
|
|
Qt::MouseButton _pressButton = Qt::LeftButton;
|
|
|
|
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
|
|
|
std::vector<std::unique_ptr<PeerListRow>> _rows;
|
|
std::map<PeerListRowId, not_null<PeerListRow*>> _rowsById;
|
|
std::map<PeerData*, std::vector<not_null<PeerListRow*>>> _rowsByPeer;
|
|
|
|
std::map<QChar, std::vector<not_null<PeerListRow*>>> _searchIndex;
|
|
QString _searchQuery;
|
|
QString _normalizedSearchQuery;
|
|
QString _mentionHighlight;
|
|
std::vector<not_null<PeerListRow*>> _filterResults;
|
|
base::flat_set<not_null<PeerListRow*>> _hiddenRows;
|
|
|
|
int _aboveHeight = 0;
|
|
int _belowHeight = 0;
|
|
bool _hideEmpty = false;
|
|
object_ptr<Ui::RpWidget> _aboveWidget = { nullptr };
|
|
object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr };
|
|
object_ptr<Ui::RpWidget> _belowWidget = { nullptr };
|
|
object_ptr<Ui::FlatLabel> _description = { nullptr };
|
|
object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
|
|
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
|
|
object_ptr<Ui::RpWidget> _loadingAnimation = { nullptr };
|
|
|
|
std::vector<std::unique_ptr<PeerListRow>> _searchRows;
|
|
base::Timer _repaintByStatus;
|
|
base::unique_qptr<Ui::PopupMenu> _contextMenu;
|
|
|
|
};
|
|
|
|
class PeerListContentDelegate : public PeerListDelegate {
|
|
public:
|
|
void setContent(PeerListContent *content) {
|
|
_content = content;
|
|
}
|
|
|
|
void peerListSetHideEmpty(bool hide) override {
|
|
_content->setHideEmpty(hide);
|
|
}
|
|
void peerListAppendRow(
|
|
std::unique_ptr<PeerListRow> row) override {
|
|
_content->appendRow(std::move(row));
|
|
}
|
|
void peerListAppendSearchRow(
|
|
std::unique_ptr<PeerListRow> row) override {
|
|
_content->appendSearchRow(std::move(row));
|
|
}
|
|
void peerListAppendFoundRow(
|
|
not_null<PeerListRow*> row) override {
|
|
_content->appendFoundRow(row);
|
|
}
|
|
void peerListPrependRow(
|
|
std::unique_ptr<PeerListRow> row) override {
|
|
_content->prependRow(std::move(row));
|
|
}
|
|
void peerListPrependRowFromSearchResult(
|
|
not_null<PeerListRow*> row) override {
|
|
_content->prependRowFromSearchResult(row);
|
|
}
|
|
PeerListRow *peerListFindRow(PeerListRowId id) override {
|
|
return _content->findRow(id);
|
|
}
|
|
std::optional<QPoint> peerListLastRowMousePosition() override {
|
|
return _content->lastRowMousePosition();
|
|
}
|
|
void peerListUpdateRow(not_null<PeerListRow*> row) override {
|
|
_content->updateRow(row);
|
|
}
|
|
void peerListRemoveRow(not_null<PeerListRow*> row) override {
|
|
_content->removeRow(row);
|
|
}
|
|
void peerListConvertRowToSearchResult(
|
|
not_null<PeerListRow*> row) override {
|
|
_content->convertRowToSearchResult(row);
|
|
}
|
|
void peerListSetRowChecked(
|
|
not_null<PeerListRow*> row,
|
|
bool checked) override {
|
|
_content->changeCheckState(row, checked, anim::type::normal);
|
|
}
|
|
void peerListSetRowHidden(
|
|
not_null<PeerListRow*> row,
|
|
bool hidden) override {
|
|
_content->setRowHidden(row, hidden);
|
|
}
|
|
void peerListSetForeignRowChecked(
|
|
not_null<PeerListRow*> row,
|
|
bool checked,
|
|
anim::type animated) override {
|
|
}
|
|
int peerListFullRowsCount() override {
|
|
return _content->fullRowsCount();
|
|
}
|
|
not_null<PeerListRow*> peerListRowAt(int index) override {
|
|
return _content->rowAt(index);
|
|
}
|
|
int peerListSearchRowsCount() override {
|
|
return _content->searchRowsCount();
|
|
}
|
|
not_null<PeerListRow*> peerListSearchRowAt(int index) override {
|
|
return _content->searchRowAt(index);
|
|
}
|
|
void peerListRefreshRows() override {
|
|
_content->refreshRows();
|
|
}
|
|
void peerListSetDescription(object_ptr<Ui::FlatLabel> description) override {
|
|
_content->setDescription(std::move(description));
|
|
}
|
|
void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) override {
|
|
_content->setSearchNoResults(std::move(noResults));
|
|
}
|
|
void peerListSetAboveWidget(object_ptr<Ui::RpWidget> aboveWidget) override {
|
|
_content->setAboveWidget(std::move(aboveWidget));
|
|
}
|
|
void peerListSetAboveSearchWidget(object_ptr<Ui::RpWidget> aboveWidget) override {
|
|
_content->setAboveSearchWidget(std::move(aboveWidget));
|
|
}
|
|
void peerListSetBelowWidget(object_ptr<Ui::RpWidget> belowWidget) override {
|
|
_content->setBelowWidget(std::move(belowWidget));
|
|
}
|
|
void peerListSetSearchMode(PeerListSearchMode mode) override {
|
|
_content->setSearchMode(mode);
|
|
}
|
|
void peerListMouseLeftGeometry() override {
|
|
_content->mouseLeftGeometry();
|
|
}
|
|
void peerListSortRows(
|
|
Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) override {
|
|
_content->reorderRows([&](
|
|
auto &&begin,
|
|
auto &&end) {
|
|
std::stable_sort(begin, end, [&](auto &&a, auto &&b) {
|
|
return compare(*a, *b);
|
|
});
|
|
});
|
|
}
|
|
int peerListPartitionRows(
|
|
Fn<bool(const PeerListRow &a)> border) override {
|
|
auto result = 0;
|
|
_content->reorderRows([&](
|
|
auto &&begin,
|
|
auto &&end) {
|
|
auto edge = std::stable_partition(begin, end, [&](
|
|
auto &¤t) {
|
|
return border(*current);
|
|
});
|
|
result = (edge - begin);
|
|
});
|
|
return result;
|
|
}
|
|
std::unique_ptr<PeerListState> peerListSaveState() const override {
|
|
return _content->saveState();
|
|
}
|
|
void peerListRestoreState(
|
|
std::unique_ptr<PeerListState> state) override {
|
|
_content->restoreState(std::move(state));
|
|
}
|
|
void peerListShowRowMenu(
|
|
not_null<PeerListRow*> row,
|
|
bool highlightRow,
|
|
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
|
|
|
|
protected:
|
|
not_null<PeerListContent*> content() const {
|
|
return _content;
|
|
}
|
|
|
|
private:
|
|
PeerListContent *_content = nullptr;
|
|
|
|
};
|
|
|
|
class PeerListContentDelegateSimple : public PeerListContentDelegate {
|
|
public:
|
|
void peerListSetTitle(rpl::producer<QString> title) override {
|
|
}
|
|
void peerListSetAdditionalTitle(rpl::producer<QString> title) override {
|
|
}
|
|
bool peerListIsRowChecked(not_null<PeerListRow*> row) override {
|
|
return false;
|
|
}
|
|
int peerListSelectedRowsCount() override {
|
|
return 0;
|
|
}
|
|
void peerListScrollToTop() override {
|
|
}
|
|
void peerListAddSelectedPeerInBunch(
|
|
not_null<PeerData*> peer) override {
|
|
Unexpected("...DelegateSimple::peerListAddSelectedPeerInBunch");
|
|
}
|
|
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override {
|
|
Unexpected("...DelegateSimple::peerListAddSelectedRowInBunch");
|
|
}
|
|
void peerListFinishSelectedRowsBunch() override {
|
|
Unexpected("...DelegateSimple::peerListFinishSelectedRowsBunch");
|
|
}
|
|
void peerListSetDescription(
|
|
object_ptr<Ui::FlatLabel> description) override {
|
|
description.destroy();
|
|
}
|
|
std::shared_ptr<Main::SessionShow> peerListUiShow() override {
|
|
Unexpected("...DelegateSimple::peerListUiShow");
|
|
}
|
|
|
|
};
|
|
|
|
class PeerListContentDelegateShow : public PeerListContentDelegateSimple {
|
|
public:
|
|
explicit PeerListContentDelegateShow(
|
|
std::shared_ptr<Main::SessionShow> show);
|
|
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
|
|
|
|
private:
|
|
std::shared_ptr<Main::SessionShow> _show;
|
|
|
|
};
|
|
|
|
class PeerListBox
|
|
: public Ui::BoxContent
|
|
, public PeerListContentDelegate {
|
|
public:
|
|
PeerListBox(
|
|
QWidget*,
|
|
std::unique_ptr<PeerListController> controller,
|
|
Fn<void(not_null<PeerListBox*>)> init);
|
|
|
|
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
|
|
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
|
|
|
void peerListSetTitle(rpl::producer<QString> title) override {
|
|
setTitle(std::move(title));
|
|
}
|
|
void peerListSetAdditionalTitle(rpl::producer<QString> title) override {
|
|
setAdditionalTitle(std::move(title));
|
|
}
|
|
void peerListSetSearchMode(PeerListSearchMode mode) override;
|
|
void peerListSetRowChecked(
|
|
not_null<PeerListRow*> row,
|
|
bool checked) override;
|
|
void peerListSetForeignRowChecked(
|
|
not_null<PeerListRow*> row,
|
|
bool checked,
|
|
anim::type animated) override;
|
|
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
|
|
int peerListSelectedRowsCount() override;
|
|
void peerListScrollToTop() override;
|
|
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
|
|
|
|
void setAddedTopScrollSkip(int skip);
|
|
|
|
void showFinished() override;
|
|
|
|
protected:
|
|
void prepare() override;
|
|
void setInnerFocus() override;
|
|
|
|
void keyPressEvent(QKeyEvent *e) override;
|
|
void resizeEvent(QResizeEvent *e) override;
|
|
void paintEvent(QPaintEvent *e) override;
|
|
|
|
private:
|
|
void peerListAddSelectedPeerInBunch(
|
|
not_null<PeerData*> peer) override {
|
|
addSelectItem(peer, anim::type::instant);
|
|
}
|
|
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override {
|
|
addSelectItem(row, anim::type::instant);
|
|
}
|
|
void peerListFinishSelectedRowsBunch() override;
|
|
|
|
void addSelectItem(
|
|
not_null<PeerData*> peer,
|
|
anim::type animated);
|
|
void addSelectItem(
|
|
not_null<PeerListRow*> row,
|
|
anim::type animated);
|
|
void addSelectItem(
|
|
uint64 itemId,
|
|
const QString &text,
|
|
PaintRoundImageCallback paintUserpic,
|
|
anim::type animated);
|
|
void createMultiSelect();
|
|
int getTopScrollSkip() const;
|
|
void updateScrollSkips();
|
|
void searchQueryChanged(const QString &query);
|
|
|
|
object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
|
|
|
|
const std::shared_ptr<Main::SessionShow> _show;
|
|
std::unique_ptr<PeerListController> _controller;
|
|
Fn<void(PeerListBox*)> _init;
|
|
bool _scrollBottomFixed = false;
|
|
int _addedTopScrollSkip = 0;
|
|
|
|
};
|