494 lines
13 KiB
C++
494 lines
13 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"
|
|
|
|
namespace Ui {
|
|
class RippleAnimation;
|
|
class RoundImageCheckbox;
|
|
class MultiSelect;
|
|
template <typename Widget>
|
|
class WidgetSlideWrap;
|
|
class FlatLabel;
|
|
} // namespace Ui
|
|
|
|
class PeerListBox : public BoxContent {
|
|
class Inner;
|
|
|
|
public:
|
|
using RowId = uint64;
|
|
|
|
class Row {
|
|
public:
|
|
Row(PeerData *peer);
|
|
Row(PeerData *peer, RowId 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;
|
|
|
|
PeerData *peer() const {
|
|
return _peer;
|
|
}
|
|
RowId id() const {
|
|
return _id;
|
|
}
|
|
|
|
void setCustomStatus(const QString &status);
|
|
void clearCustomStatus();
|
|
|
|
virtual ~Row();
|
|
|
|
protected:
|
|
virtual void paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected);
|
|
|
|
bool isInitialized() const {
|
|
return _initialized;
|
|
}
|
|
virtual void lazyInitialize();
|
|
|
|
private:
|
|
// Inner interface.
|
|
friend class PeerListBox;
|
|
friend class Inner;
|
|
|
|
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 isGlobalSearchResult() const {
|
|
return _isGlobalSearchResult;
|
|
}
|
|
void setIsGlobalSearchResult(bool isGlobalSearchResult) {
|
|
_isGlobalSearchResult = isGlobalSearchResult;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private:
|
|
void createCheckbox(base::lambda<void()> updateCallback);
|
|
void setCheckedInternal(bool checked, SetStyle style);
|
|
void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const;
|
|
|
|
RowId _id = 0;
|
|
PeerData *_peer = nullptr;
|
|
bool _initialized = false;
|
|
std::unique_ptr<Ui::RippleAnimation> _ripple;
|
|
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
|
|
Text _name;
|
|
QString _status;
|
|
StatusType _statusType = StatusType::Online;
|
|
bool _disabled = false;
|
|
int _absoluteIndex = -1;
|
|
OrderedSet<QChar> _nameFirstChars;
|
|
bool _isGlobalSearchResult = false;
|
|
|
|
};
|
|
|
|
class Controller {
|
|
public:
|
|
virtual void prepare() = 0;
|
|
virtual void rowClicked(Row *row) = 0;
|
|
virtual void rowActionClicked(Row *row) {
|
|
}
|
|
virtual void preloadRows() {
|
|
}
|
|
virtual std::unique_ptr<Row> createGlobalRow(PeerData *peer) {
|
|
return std::unique_ptr<Row>();
|
|
}
|
|
|
|
virtual ~Controller() = default;
|
|
|
|
protected:
|
|
PeerListBox *view() const {
|
|
return _view;
|
|
}
|
|
|
|
private:
|
|
void setView(PeerListBox *box) {
|
|
_view = box;
|
|
prepare();
|
|
}
|
|
|
|
PeerListBox *_view = nullptr;
|
|
|
|
friend class PeerListBox;
|
|
friend class Inner;
|
|
|
|
};
|
|
PeerListBox(QWidget*, std::unique_ptr<Controller> controller);
|
|
|
|
// Interface for the controller.
|
|
void appendRow(std::unique_ptr<Row> row);
|
|
void prependRow(std::unique_ptr<Row> row);
|
|
Row *findRow(RowId id);
|
|
void updateRow(Row *row);
|
|
void removeRow(Row *row);
|
|
void setRowChecked(Row *row, bool checked);
|
|
int fullRowsCount() const;
|
|
Row *rowAt(int index) const;
|
|
void setAboutText(const QString &aboutText);
|
|
void setAbout(object_ptr<Ui::FlatLabel> about);
|
|
void refreshRows();
|
|
|
|
// Search works only with RowId == peer->id.
|
|
enum class SearchMode {
|
|
None,
|
|
Local,
|
|
Global,
|
|
};
|
|
void setSearchMode(SearchMode mode);
|
|
void setSearchNoResultsText(const QString &noResultsText);
|
|
void setSearchNoResults(object_ptr<Ui::FlatLabel> searchNoResults);
|
|
void setSearchLoadingText(const QString &searchLoadingText);
|
|
void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading);
|
|
|
|
template <typename PeerDataRange>
|
|
void addSelectedRows(PeerDataRange &&range) {
|
|
Expects(_select != nullptr);
|
|
for (auto peer : range) {
|
|
addSelectItem(peer, Row::SetStyle::Fast);
|
|
}
|
|
finishSelectItemsBunch();
|
|
}
|
|
QVector<PeerData*> collectSelectedRows() const;
|
|
|
|
// callback takes two iterators, like [](auto &begin, auto &end).
|
|
template <typename ReorderCallback>
|
|
void reorderRows(ReorderCallback &&callback);
|
|
|
|
bool isRowSelected(PeerData *peer) const;
|
|
|
|
protected:
|
|
void prepare() override;
|
|
void setInnerFocus() override;
|
|
|
|
void keyPressEvent(QKeyEvent *e) override;
|
|
void resizeEvent(QResizeEvent *e) override;
|
|
|
|
private:
|
|
void addSelectItem(PeerData *peer, Row::SetStyle style);
|
|
void finishSelectItemsBunch();
|
|
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<Controller> _controller;
|
|
|
|
};
|
|
|
|
// This class is hold in header because it requires Qt preprocessing.
|
|
class PeerListBox::Inner : public TWidget, private MTP::Sender, private base::Subscriber {
|
|
Q_OBJECT
|
|
|
|
public:
|
|
Inner(QWidget *parent, Controller *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<Row> row);
|
|
void prependRow(std::unique_ptr<Row> row);
|
|
Row *findRow(RowId id);
|
|
void updateRow(Row *row) {
|
|
updateRow(row, RowIndex());
|
|
}
|
|
void removeRow(Row *row);
|
|
int fullRowsCount() const;
|
|
Row *rowAt(int index) const;
|
|
void setAbout(object_ptr<Ui::FlatLabel> about);
|
|
void refreshRows();
|
|
void setSearchMode(SearchMode mode);
|
|
void setSearchNoResults(object_ptr<Ui::FlatLabel> searchNoResults);
|
|
void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading);
|
|
|
|
void changeCheckState(Row *row, bool checked, Row::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 appendGlobalSearchRow(std::unique_ptr<Row> row);
|
|
|
|
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(Row *row, RowIndex hint);
|
|
void updateRow(RowIndex row);
|
|
int getRowTop(RowIndex row) const;
|
|
Row *getRow(RowIndex element);
|
|
RowIndex findRowIndex(Row *row, RowIndex hint = RowIndex());
|
|
QRect getActionRect(Row *row, RowIndex index) const;
|
|
|
|
void paintRow(Painter &p, TimeMs ms, RowIndex index);
|
|
|
|
void addRowEntry(Row *row);
|
|
void addToSearchIndex(Row *row);
|
|
bool addingToSearchIndex() const;
|
|
void removeFromSearchIndex(Row *row);
|
|
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 needGlobalSearch();
|
|
bool globalSearchInCache();
|
|
void globalSearchOnServer();
|
|
void globalSearchDone(const MTPcontacts_Found &result, mtpRequestId requestId);
|
|
bool globalSearchLoading() const;
|
|
void clearGlobalSearchRows();
|
|
|
|
Controller *_controller = nullptr;
|
|
int _rowHeight = 0;
|
|
int _visibleTop = 0;
|
|
int _visibleBottom = 0;
|
|
|
|
Selected _selected;
|
|
Selected _pressed;
|
|
bool _mouseSelection = false;
|
|
|
|
std::vector<std::unique_ptr<Row>> _rows;
|
|
std::map<RowId, Row*> _rowsById;
|
|
std::map<PeerData*, std::vector<Row*>> _rowsByPeer;
|
|
|
|
SearchMode _searchMode = SearchMode::None;
|
|
std::map<QChar, std::vector<Row*>> _searchIndex;
|
|
QString _searchQuery;
|
|
std::vector<Row*> _filterResults;
|
|
|
|
object_ptr<Ui::FlatLabel> _about = { nullptr };
|
|
object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
|
|
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
|
|
|
|
QPoint _lastMousePosition;
|
|
|
|
std::vector<std::unique_ptr<Row>> _globalSearchRows;
|
|
object_ptr<SingleTimer> _globalSearchTimer = { nullptr };
|
|
QString _globalSearchQuery;
|
|
QString _globalSearchHighlight;
|
|
mtpRequestId _globalSearchRequestId = 0;
|
|
std::map<QString, MTPcontacts_Found> _globalSearchCache;
|
|
std::map<mtpRequestId, QString> _globalSearchQueries;
|
|
|
|
};
|
|
|
|
template <typename ReorderCallback>
|
|
inline void PeerListBox::reorderRows(ReorderCallback &&callback) {
|
|
_inner->reorderRows(std::forward<ReorderCallback>(callback));
|
|
}
|
|
|
|
class PeerListRowWithLink : public PeerListBox::Row {
|
|
public:
|
|
using Row::Row;
|
|
|
|
void setActionLink(const QString &action);
|
|
|
|
protected:
|
|
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 ChatsListBoxController : public PeerListBox::Controller, protected base::Subscriber {
|
|
public:
|
|
void prepare() override final;
|
|
std::unique_ptr<PeerListBox::Row> createGlobalRow(PeerData *peer) override final;
|
|
|
|
protected:
|
|
class Row : public PeerListBox::Row {
|
|
public:
|
|
Row(History *history) : PeerListBox::Row(history->peer), _history(history) {
|
|
}
|
|
History *history() const {
|
|
return _history;
|
|
}
|
|
|
|
private:
|
|
History *_history = nullptr;
|
|
|
|
};
|
|
virtual std::unique_ptr<Row> createRow(History *history) = 0;
|
|
virtual void prepareViewHook() = 0;
|
|
virtual void updateRowHook(Row *row) {
|
|
}
|
|
|
|
private:
|
|
void rebuildRows();
|
|
void checkForEmptyRows();
|
|
bool appendRow(History *history);
|
|
|
|
};
|