mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 06:26:18 +00:00
Select exception users in EditPrivacyBox.
This commit is contained in:
@@ -1012,7 +1012,7 @@ void ContactsBox::Inner::paintDisabledCheckUserpic(Painter &p, PeerData *peer, i
|
||||
auto iconBorderPen = st::contactsPhotoCheckbox.check.border->p;
|
||||
iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth);
|
||||
|
||||
peer->paintUserpicLeft(p, userpicLeft, userpicTop, width(), userpicRadius * 2);
|
||||
peer->paintUserpicLeft(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
@@ -1446,7 +1446,7 @@ void ContactsBox::Inner::changeCheckState(Dialogs::Row *row) {
|
||||
}
|
||||
|
||||
void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) {
|
||||
t_assert(usingMultiSelect());
|
||||
Expects(usingMultiSelect());
|
||||
|
||||
if (isRowDisabled(peer, data)) {
|
||||
} else if (data->checkbox->checked()) {
|
||||
|
@@ -25,8 +25,69 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/effects/widget_slide_wrap.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "lang.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class PrivacyExceptionsBoxController : public ChatsListBoxController {
|
||||
public:
|
||||
PrivacyExceptionsBoxController(const QString &title, const QVector<UserData*> &selected, base::lambda_once<void(QVector<UserData*> &&result)> saveCallback);
|
||||
void rowClicked(PeerListBox::Row *row) override;
|
||||
|
||||
protected:
|
||||
void prepareViewHook() override;
|
||||
std::unique_ptr<Row> createRow(History *history) override;
|
||||
|
||||
private:
|
||||
QString _title;
|
||||
QVector<UserData*> _selected;
|
||||
base::lambda_once<void(QVector<UserData*> &&result)> _saveCallback;
|
||||
|
||||
};
|
||||
|
||||
PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(const QString &title, const QVector<UserData*> &selected, base::lambda_once<void(QVector<UserData*> &&result)> saveCallback)
|
||||
: _title(title)
|
||||
, _selected(selected)
|
||||
, _saveCallback(std::move(saveCallback)) {
|
||||
}
|
||||
|
||||
void PrivacyExceptionsBoxController::prepareViewHook() {
|
||||
view()->setTitle(_title);
|
||||
view()->addButton(lang(lng_settings_save), [this] {
|
||||
auto peers = view()->collectSelectedRows();
|
||||
auto users = QVector<UserData*>();
|
||||
if (!peers.empty()) {
|
||||
users.reserve(peers.size());
|
||||
for_const (auto peer, peers) {
|
||||
auto user = peer->asUser();
|
||||
t_assert(user != nullptr);
|
||||
users.push_back(user);
|
||||
}
|
||||
}
|
||||
_saveCallback(std::move(users));
|
||||
view()->closeBox();
|
||||
});
|
||||
view()->addButton(lang(lng_cancel), [this] { view()->closeBox(); });
|
||||
view()->addSelectedRows(_selected);
|
||||
}
|
||||
|
||||
void PrivacyExceptionsBoxController::rowClicked(PeerListBox::Row *row) {
|
||||
view()->setRowChecked(row, !row->checked());
|
||||
view()->updateRow(row);
|
||||
}
|
||||
|
||||
std::unique_ptr<PrivacyExceptionsBoxController::Row> PrivacyExceptionsBoxController::createRow(History *history) {
|
||||
if (auto user = history->peer->asUser()) {
|
||||
if (!user->isSelf()) {
|
||||
return std::make_unique<Row>(history);
|
||||
}
|
||||
}
|
||||
return std::unique_ptr<Row>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class EditPrivacyBox::OptionWidget : public TWidget {
|
||||
public:
|
||||
OptionWidget(QWidget *parent, int value, bool selected, const QString &text, const QString &description);
|
||||
@@ -149,12 +210,34 @@ int EditPrivacyBox::countDefaultHeight(int newWidth) {
|
||||
return height;
|
||||
}
|
||||
|
||||
void EditPrivacyBox::editAlwaysUsers() {
|
||||
// not implemented
|
||||
void EditPrivacyBox::editExceptionUsers(Exception exception) {
|
||||
auto controller = std::make_unique<PrivacyExceptionsBoxController>(_controller->exceptionBoxTitle(exception), exceptionUsers(exception), base::lambda_guarded(this, [this, exception](QVector<UserData*> &&users) {
|
||||
exceptionUsers(exception) = std::move(users);
|
||||
exceptionLink(exception)->entity()->setText(exceptionLinkText(exception));
|
||||
auto removeFrom = ([exception] {
|
||||
switch (exception) {
|
||||
case Exception::Always: return Exception::Never;
|
||||
case Exception::Never: return Exception::Always;
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
})();
|
||||
auto &removeFromUsers = exceptionUsers(removeFrom);
|
||||
auto removedSome = false;
|
||||
for (auto user : exceptionUsers(exception)) {
|
||||
if (removeFromUsers.contains(user)) {
|
||||
removeFromUsers.erase(std::remove(removeFromUsers.begin(), removeFromUsers.end(), user), removeFromUsers.end());
|
||||
removedSome = true;
|
||||
}
|
||||
}
|
||||
if (removedSome) {
|
||||
exceptionLink(removeFrom)->entity()->setText(exceptionLinkText(removeFrom));
|
||||
}
|
||||
}));
|
||||
Ui::show(Box<PeerListBox>(std::move(controller)), KeepOtherLayers);
|
||||
}
|
||||
|
||||
void EditPrivacyBox::editNeverUsers() {
|
||||
// not implemented
|
||||
QString EditPrivacyBox::exceptionLinkText(Exception exception) {
|
||||
return _controller->exceptionLinkText(exception, exceptionUsers(exception).size());
|
||||
}
|
||||
|
||||
QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
|
||||
@@ -169,10 +252,10 @@ QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
|
||||
|
||||
auto result = QVector<MTPInputPrivacyRule>();
|
||||
result.reserve(3);
|
||||
if (showAlwaysLink() && !_alwaysUsers.empty()) {
|
||||
if (showExceptionLink(Exception::Always) && !_alwaysUsers.empty()) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_alwaysUsers))));
|
||||
}
|
||||
if (showNeverLink() && !_neverUsers.empty()) {
|
||||
if (showExceptionLink(Exception::Never) && !_neverUsers.empty()) {
|
||||
result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_neverUsers))));
|
||||
}
|
||||
switch (_option) {
|
||||
@@ -188,12 +271,28 @@ style::margins EditPrivacyBox::exceptionLinkMargins() const {
|
||||
return st::editPrivacyLinkMargin;
|
||||
}
|
||||
|
||||
bool EditPrivacyBox::showAlwaysLink() const {
|
||||
return (_option == Option::Contacts) || (_option == Option::Nobody);
|
||||
QVector<UserData*> &EditPrivacyBox::exceptionUsers(Exception exception) {
|
||||
switch (exception) {
|
||||
case Exception::Always: return _alwaysUsers;
|
||||
case Exception::Never: return _neverUsers;
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}
|
||||
|
||||
bool EditPrivacyBox::showNeverLink() const {
|
||||
return (_option == Option::Everyone) || (_option == Option::Contacts);
|
||||
object_ptr<Ui::WidgetSlideWrap<Ui::LinkButton>> &EditPrivacyBox::exceptionLink(Exception exception) {
|
||||
switch (exception) {
|
||||
case Exception::Always: return _alwaysLink;
|
||||
case Exception::Never: return _neverLink;
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}
|
||||
|
||||
bool EditPrivacyBox::showExceptionLink(Exception exception) const {
|
||||
switch (exception) {
|
||||
case Exception::Always: return (_option == Option::Contacts) || (_option == Option::Nobody);
|
||||
case Exception::Never: return (_option == Option::Everyone) || (_option == Option::Contacts);
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}
|
||||
|
||||
void EditPrivacyBox::createOption(Option option, object_ptr<OptionWidget> &widget, const QString &label) {
|
||||
@@ -205,8 +304,8 @@ void EditPrivacyBox::createOption(Option option, object_ptr<OptionWidget> &widge
|
||||
widget->setChangedCallback([this, option, widget = widget.data()] {
|
||||
if (widget->checked()) {
|
||||
_option = option;
|
||||
_alwaysLink->toggleAnimated(showAlwaysLink());
|
||||
_neverLink->toggleAnimated(showNeverLink());
|
||||
_alwaysLink->toggleAnimated(showExceptionLink(Exception::Always));
|
||||
_neverLink->toggleAnimated(showExceptionLink(Exception::Never));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -221,22 +320,23 @@ void EditPrivacyBox::createWidgets() {
|
||||
_description.create(this, _controller->description(), Ui::FlatLabel::InitType::Simple, st::editPrivacyLabel);
|
||||
|
||||
_exceptionsTitle.create(this, lang(lng_edit_privacy_exceptions), Ui::FlatLabel::InitType::Simple, st::editPrivacyTitle);
|
||||
auto linkResizedCallback = [this] {
|
||||
resizeGetHeight(width());
|
||||
auto createExceptionLink = [this](Exception exception) {
|
||||
exceptionLink(exception).create(this, object_ptr<Ui::LinkButton>(this, exceptionLinkText(exception)), exceptionLinkMargins(), [this] {
|
||||
resizeGetHeight(width());
|
||||
});
|
||||
exceptionLink(exception)->entity()->setClickedCallback([this, exception] { editExceptionUsers(exception); });
|
||||
};
|
||||
_alwaysLink.create(this, object_ptr<Ui::LinkButton>(this, _controller->alwaysLinkText(_alwaysUsers.size())), exceptionLinkMargins(), linkResizedCallback);
|
||||
_alwaysLink->entity()->setClickedCallback([this] { editAlwaysUsers(); });
|
||||
_neverLink.create(this, object_ptr<Ui::LinkButton>(this, _controller->neverLinkText(_neverUsers.size())), exceptionLinkMargins(), linkResizedCallback);
|
||||
_neverLink->entity()->setClickedCallback([this] { editNeverUsers(); });
|
||||
createExceptionLink(Exception::Always);
|
||||
createExceptionLink(Exception::Never);
|
||||
_exceptionsDescription.create(this, _controller->exceptionsDescription(), Ui::FlatLabel::InitType::Simple, st::editPrivacyLabel);
|
||||
|
||||
addButton(lang(lng_settings_save), [this] {
|
||||
_controller->save(collectResult());
|
||||
});
|
||||
clearButtons();
|
||||
addButton(lang(lng_settings_save), [this] { _controller->save(collectResult()); });
|
||||
addButton(lang(lng_cancel), [this] { closeBox(); });
|
||||
|
||||
showChildren();
|
||||
_alwaysLink->toggleFast(showAlwaysLink());
|
||||
_neverLink->toggleFast(showNeverLink());
|
||||
_alwaysLink->toggleFast(showExceptionLink(Exception::Always));
|
||||
_neverLink->toggleFast(showExceptionLink(Exception::Never));
|
||||
|
||||
setDimensions(st::boxWideWidth, resizeGetHeight(st::boxWideWidth));
|
||||
}
|
||||
|
@@ -32,10 +32,14 @@ class WidgetSlideWrap;
|
||||
class EditPrivacyBox : public BoxContent {
|
||||
public:
|
||||
enum class Option {
|
||||
Everyone = 0,
|
||||
Everyone,
|
||||
Contacts,
|
||||
Nobody,
|
||||
};
|
||||
enum class Exception {
|
||||
Always,
|
||||
Never,
|
||||
};
|
||||
|
||||
class Controller {
|
||||
public:
|
||||
@@ -47,8 +51,8 @@ public:
|
||||
return QString();
|
||||
}
|
||||
virtual QString description() = 0;
|
||||
virtual QString alwaysLinkText(int count) = 0;
|
||||
virtual QString neverLinkText(int count) = 0;
|
||||
virtual QString exceptionLinkText(Exception exception, int count) = 0;
|
||||
virtual QString exceptionBoxTitle(Exception exception) = 0;
|
||||
virtual QString exceptionsDescription() = 0;
|
||||
|
||||
virtual ~Controller() = default;
|
||||
@@ -81,17 +85,20 @@ private:
|
||||
class OptionWidget;
|
||||
|
||||
style::margins exceptionLinkMargins() const;
|
||||
bool showAlwaysLink() const;
|
||||
bool showNeverLink() const;
|
||||
bool showExceptionLink(Exception exception) const;
|
||||
void createWidgets();
|
||||
void createOption(Option option, object_ptr<OptionWidget> &widget, const QString &label);
|
||||
QVector<MTPInputPrivacyRule> collectResult();
|
||||
void loadDone(const MTPaccount_PrivacyRules &result);
|
||||
int countDefaultHeight(int newWidth);
|
||||
void editAlwaysUsers();
|
||||
void editNeverUsers();
|
||||
|
||||
void editExceptionUsers(Exception exception);
|
||||
QString exceptionLinkText(Exception exception);
|
||||
QVector<UserData*> &exceptionUsers(Exception exception);
|
||||
object_ptr<Ui::WidgetSlideWrap<Ui::LinkButton>> &exceptionLink(Exception exception);
|
||||
|
||||
std::unique_ptr<Controller> _controller;
|
||||
Option _option = Option::Everyone;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _loading;
|
||||
object_ptr<OptionWidget> _everyone = { nullptr };
|
||||
@@ -105,7 +112,6 @@ private:
|
||||
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
|
||||
Option _option = Option::Everyone;
|
||||
QVector<UserData*> _alwaysUsers;
|
||||
QVector<UserData*> _neverUsers;
|
||||
|
||||
|
@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "observer_peer.h"
|
||||
#include "auth_session.h"
|
||||
#include "mainwidget.h"
|
||||
@@ -32,6 +33,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/effects/widget_slide_wrap.h"
|
||||
#include "lang.h"
|
||||
#include "ui/effects/round_checkbox.h"
|
||||
#include "boxes/contactsbox.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
|
||||
PeerListBox::PeerListBox(QWidget*, std::unique_ptr<Controller> controller)
|
||||
: _controller(std::move(controller)) {
|
||||
@@ -132,6 +136,17 @@ void PeerListBox::removeRow(Row *row) {
|
||||
_inner->removeRow(row);
|
||||
}
|
||||
|
||||
void PeerListBox::setRowChecked(Row *row, bool checked) {
|
||||
auto peer = row->peer();
|
||||
if (checked) {
|
||||
addSelectItem(peer, Row::SetStyle::Animated);
|
||||
_inner->changeCheckState(row, checked, Row::SetStyle::Animated);
|
||||
} else {
|
||||
// The itemRemovedCallback will call changeCheckState() here.
|
||||
_select->entity()->removeItem(peer->id);
|
||||
}
|
||||
}
|
||||
|
||||
int PeerListBox::fullRowsCount() const {
|
||||
return _inner->fullRowsCount();
|
||||
}
|
||||
@@ -158,7 +173,15 @@ void PeerListBox::setSearchMode(SearchMode mode) {
|
||||
_select = createMultiSelect();
|
||||
_select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { _inner->submitted(); });
|
||||
_select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); });
|
||||
_select->resizeToWidth(width());
|
||||
_select->entity()->setItemRemovedCallback([this](uint64 itemId) {
|
||||
if (auto peer = App::peerLoaded(itemId)) {
|
||||
if (auto row = findRow(peer)) {
|
||||
_inner->changeCheckState(row, false, Row::SetStyle::Animated);
|
||||
update();
|
||||
}
|
||||
}
|
||||
});
|
||||
_select->resizeToWidth(st::boxWideWidth);
|
||||
_select->moveToLeft(0, 0);
|
||||
}
|
||||
if (_select) {
|
||||
@@ -190,11 +213,43 @@ void PeerListBox::setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading) {
|
||||
_inner->setSearchLoading(std::move(searchLoading));
|
||||
}
|
||||
|
||||
QVector<PeerData*> PeerListBox::collectSelectedRows() const {
|
||||
Expects(_select != nullptr);
|
||||
auto result = QVector<PeerData*>();
|
||||
auto items = _select->entity()->getItems();
|
||||
if (!items.empty()) {
|
||||
result.reserve(items.size());
|
||||
for_const (auto itemId, items) {
|
||||
result.push_back(App::peer(itemId));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void PeerListBox::addSelectItem(PeerData *peer, Row::SetStyle style) {
|
||||
Expects(_select != nullptr);
|
||||
if (style == Row::SetStyle::Fast) {
|
||||
_select->entity()->addItemInBunch(peer->id, peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer));
|
||||
} else {
|
||||
_select->entity()->addItem(peer->id, peer->shortName(), st::activeButtonBg, PaintUserpicCallback(peer), Ui::MultiSelect::AddItemWay::Default);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::finishSelectItemsBunch() {
|
||||
Expects(_select != nullptr);
|
||||
_select->entity()->finishItemsBunch();
|
||||
}
|
||||
|
||||
bool PeerListBox::isRowSelected(PeerData *peer) const {
|
||||
Expects(_select != nullptr);
|
||||
return _select->entity()->hasItem(peer->id);
|
||||
}
|
||||
|
||||
PeerListBox::Row::Row(PeerData *peer) : _peer(peer) {
|
||||
}
|
||||
|
||||
void PeerListBox::Row::setDisabled(bool disabled) {
|
||||
_disabled = disabled;
|
||||
bool PeerListBox::Row::checked() const {
|
||||
return _checkbox && _checkbox->checked();
|
||||
}
|
||||
|
||||
void PeerListBox::Row::setActionLink(const QString &action) {
|
||||
@@ -253,6 +308,12 @@ int PeerListBox::Row::actionWidth() const {
|
||||
|
||||
PeerListBox::Row::~Row() = default;
|
||||
|
||||
void PeerListBox::Row::invalidatePixmapsCache() {
|
||||
if (_checkbox) {
|
||||
_checkbox->invalidateCache();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename UpdateCallback>
|
||||
void PeerListBox::Row::addRipple(QSize size, QPoint point, UpdateCallback updateCallback) {
|
||||
if (!_ripple) {
|
||||
@@ -268,7 +329,7 @@ void PeerListBox::Row::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::Row::paintRipple(Painter &p, int x, int y, int outerWidth, TimeMs ms) {
|
||||
void PeerListBox::Row::paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth) {
|
||||
if (_ripple) {
|
||||
_ripple->paint(p, x, y, outerWidth, ms);
|
||||
if (_ripple->empty()) {
|
||||
@@ -277,6 +338,57 @@ void PeerListBox::Row::paintRipple(Painter &p, int x, int y, int outerWidth, Tim
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::Row::paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth) {
|
||||
if (_checkbox) {
|
||||
if (disabled() && checked()) {
|
||||
paintDisabledCheckUserpic(p, x, y, outerWidth);
|
||||
} else {
|
||||
_checkbox->paint(p, ms, x, y, outerWidth);
|
||||
}
|
||||
} else {
|
||||
peer()->paintUserpicLeft(p, x, y, outerWidth, st::contactsPhotoSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Emulates Ui::RoundImageCheckbox::paint() in a checked state.
|
||||
void PeerListBox::Row::paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const {
|
||||
auto userpicRadius = st::contactsPhotoCheckbox.imageSmallRadius;
|
||||
auto userpicShift = st::contactsPhotoCheckbox.imageRadius - userpicRadius;
|
||||
auto userpicDiameter = st::contactsPhotoCheckbox.imageRadius * 2;
|
||||
auto userpicLeft = x + userpicShift;
|
||||
auto userpicTop = y + userpicShift;
|
||||
auto userpicEllipse = rtlrect(x, y, userpicDiameter, userpicDiameter, outerWidth);
|
||||
auto userpicBorderPen = st::contactsPhotoDisabledCheckFg->p;
|
||||
userpicBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth);
|
||||
|
||||
auto iconDiameter = st::contactsPhotoCheckbox.check.size;
|
||||
auto iconLeft = x + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter;
|
||||
auto iconTop = y + userpicDiameter + st::contactsPhotoCheckbox.selectWidth - iconDiameter;
|
||||
auto iconEllipse = rtlrect(iconLeft, iconTop, iconDiameter, iconDiameter, outerWidth);
|
||||
auto iconBorderPen = st::contactsPhotoCheckbox.check.border->p;
|
||||
iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth);
|
||||
|
||||
peer()->paintUserpicLeft(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
p.setPen(userpicBorderPen);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(userpicEllipse);
|
||||
|
||||
p.setPen(iconBorderPen);
|
||||
p.setBrush(st::contactsPhotoDisabledCheckFg);
|
||||
p.drawEllipse(iconEllipse);
|
||||
}
|
||||
|
||||
st::contactsPhotoCheckbox.check.check.paint(p, iconEllipse.topLeft(), outerWidth);
|
||||
}
|
||||
|
||||
float64 PeerListBox::Row::checkedRatio() {
|
||||
return _checkbox ? _checkbox->checkedAnimationRatio() : 0.;
|
||||
}
|
||||
|
||||
void PeerListBox::Row::lazyInitialize() {
|
||||
if (_initialized) {
|
||||
return;
|
||||
@@ -287,6 +399,17 @@ void PeerListBox::Row::lazyInitialize() {
|
||||
refreshStatus();
|
||||
}
|
||||
|
||||
void PeerListBox::Row::createCheckbox(base::lambda<void()> updateCallback) {
|
||||
_checkbox = std::make_unique<Ui::RoundImageCheckbox>(st::contactsPhotoCheckbox, std::move(updateCallback), PaintUserpicCallback(_peer));
|
||||
}
|
||||
|
||||
void PeerListBox::Row::setCheckedInternal(bool checked, SetStyle style) {
|
||||
Expects(_checkbox != nullptr);
|
||||
using CheckboxStyle = Ui::RoundCheckbox::SetStyle;
|
||||
auto speed = (style == SetStyle::Animated) ? CheckboxStyle::Animated : CheckboxStyle::Fast;
|
||||
_checkbox->setChecked(checked, speed);
|
||||
}
|
||||
|
||||
PeerListBox::Inner::Inner(QWidget *parent, Controller *controller) : TWidget(parent)
|
||||
, _controller(controller)
|
||||
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) {
|
||||
@@ -294,6 +417,12 @@ PeerListBox::Inner::Inner(QWidget *parent, Controller *controller) : TWidget(par
|
||||
|
||||
connect(App::main(), SIGNAL(peerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)), this, SLOT(onPeerNameChanged(PeerData*, const PeerData::Names&, const PeerData::NameFirstChars&)));
|
||||
connect(App::main(), SIGNAL(peerPhotoChanged(PeerData*)), this, SLOT(peerUpdated(PeerData*)));
|
||||
|
||||
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &update) {
|
||||
if (update.paletteChanged()) {
|
||||
invalidatePixmapsCache();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PeerListBox::Inner::appendRow(std::unique_ptr<Row> row) {
|
||||
@@ -305,7 +434,7 @@ void PeerListBox::Inner::appendRow(std::unique_ptr<Row> row) {
|
||||
}
|
||||
|
||||
void PeerListBox::Inner::appendGlobalSearchRow(std::unique_ptr<Row> row) {
|
||||
t_assert(showingSearch());
|
||||
Expects(showingSearch());
|
||||
if (_rowsByPeer.find(row->peer()) == _rowsByPeer.cend()) {
|
||||
row->setAbsoluteIndex(_globalSearchRows.size());
|
||||
row->setIsGlobalSearchResult(true);
|
||||
@@ -315,11 +444,31 @@ void PeerListBox::Inner::appendGlobalSearchRow(std::unique_ptr<Row> row) {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::Inner::changeCheckState(Row *row, bool checked, Row::SetStyle style) {
|
||||
row->setChecked(checked, style, [this, row] {
|
||||
updateRow(row);
|
||||
});
|
||||
}
|
||||
|
||||
void PeerListBox::Inner::addRowEntry(Row *row) {
|
||||
_rowsByPeer.emplace(row->peer(), row);
|
||||
if (addingToSearchIndex()) {
|
||||
addToSearchIndex(row);
|
||||
}
|
||||
if (_searchMode != SearchMode::None) {
|
||||
if (_controller->view()->isRowSelected(row->peer())) {
|
||||
changeCheckState(row, true, Row::SetStyle::Fast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::Inner::invalidatePixmapsCache() {
|
||||
for_const (auto &row, _rows) {
|
||||
row->invalidatePixmapsCache();
|
||||
}
|
||||
for_const (auto &row, _globalSearchRows) {
|
||||
row->invalidatePixmapsCache();
|
||||
}
|
||||
}
|
||||
|
||||
bool PeerListBox::Inner::addingToSearchIndex() const {
|
||||
@@ -554,11 +703,10 @@ void PeerListBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
setPressed(Selected());
|
||||
if (e->button() == Qt::LeftButton && pressed == _selected) {
|
||||
if (auto row = getRow(pressed.index)) {
|
||||
auto peer = row->peer();
|
||||
if (pressed.action) {
|
||||
_controller->rowActionClicked(peer);
|
||||
_controller->rowActionClicked(row);
|
||||
} else {
|
||||
_controller->rowClicked(peer);
|
||||
_controller->rowClicked(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -583,8 +731,8 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) {
|
||||
auto actionSelected = (selected && active.action);
|
||||
|
||||
p.fillRect(0, 0, width(), _rowHeight, selected ? st::contactsBgOver : st::contactsBg);
|
||||
row->paintRipple(p, 0, 0, width(), ms);
|
||||
peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize);
|
||||
row->paintRipple(p, ms, 0, 0, width());
|
||||
row->paintUserpic(p, ms, st::contactsPadding.left(), st::contactsPadding.top(), width());
|
||||
|
||||
p.setPen(st::contactsNameFg);
|
||||
|
||||
@@ -597,6 +745,7 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) {
|
||||
namew -= icon->width();
|
||||
icon->paint(p, namex + qMin(name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width());
|
||||
}
|
||||
p.setPen(anim::pen(st::contactsNameFg, st::contactsNameCheckedFg, row->checkedRatio()));
|
||||
name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width());
|
||||
|
||||
if (actionWidth) {
|
||||
@@ -885,7 +1034,7 @@ bool PeerListBox::Inner::globalSearchLoading() const {
|
||||
|
||||
void PeerListBox::Inner::submitted() {
|
||||
if (auto row = getRow(_selected.index)) {
|
||||
_controller->rowClicked(row->peer());
|
||||
_controller->rowClicked(row);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,17 +1103,19 @@ void PeerListBox::Inner::updateRow(Row *row, RowIndex 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);
|
||||
if (index.value < 0) {
|
||||
return;
|
||||
}
|
||||
auto row = getRow(index);
|
||||
if (row->disabled()) {
|
||||
if (index == _selected.index) {
|
||||
setSelected(Selected());
|
||||
}
|
||||
if (index == _pressed.index) {
|
||||
setPressed(Selected());
|
||||
}
|
||||
}
|
||||
update(0, getRowTop(index), width(), _rowHeight);
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
@@ -1037,3 +1188,79 @@ void PeerListBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names
|
||||
updateRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatsListBoxController::prepare() {
|
||||
view()->setSearchNoResultsText(lang(lng_blocked_list_not_found));
|
||||
view()->setSearchMode(PeerListBox::SearchMode::Global);
|
||||
|
||||
prepareViewHook();
|
||||
|
||||
rebuildRows();
|
||||
|
||||
auto &sessionData = AuthSession::Current().data();
|
||||
subscribe(sessionData.contactsLoaded(), [this](bool loaded) {
|
||||
rebuildRows();
|
||||
});
|
||||
subscribe(sessionData.moreChatsLoaded(), [this] {
|
||||
rebuildRows();
|
||||
});
|
||||
subscribe(sessionData.allChatsLoaded(), [this](bool loaded) {
|
||||
checkForEmptyRows();
|
||||
});
|
||||
}
|
||||
|
||||
void ChatsListBoxController::rebuildRows() {
|
||||
auto ms = getms();
|
||||
auto wasEmpty = !view()->fullRowsCount();
|
||||
auto appendList = [this](auto chats) {
|
||||
auto count = 0;
|
||||
for_const (auto row, chats->all()) {
|
||||
auto history = row->history();
|
||||
if (history->peer->isUser()) {
|
||||
if (appendRow(history)) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
auto added = appendList(App::main()->dialogsList());
|
||||
added += appendList(App::main()->contactsNoDialogsList());
|
||||
if (!wasEmpty && added > 0) {
|
||||
view()->reorderRows([](auto &&begin, auto &&end) {
|
||||
// Place dialogs list before contactsNoDialogs list.
|
||||
std::stable_partition(begin, end, [](auto &row) {
|
||||
auto history = static_cast<Row&>(*row).history();
|
||||
return history->inChatList(Dialogs::Mode::All);
|
||||
});
|
||||
});
|
||||
}
|
||||
checkForEmptyRows();
|
||||
view()->refreshRows();
|
||||
}
|
||||
|
||||
void ChatsListBoxController::checkForEmptyRows() {
|
||||
if (view()->fullRowsCount()) {
|
||||
view()->setAboutText(QString());
|
||||
} else {
|
||||
auto &sessionData = AuthSession::Current().data();
|
||||
auto loaded = sessionData.contactsLoaded().value() && sessionData.allChatsLoaded().value();
|
||||
view()->setAboutText(lang(loaded ? lng_contacts_not_found : lng_contacts_loading));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListBox::Row> ChatsListBoxController::createGlobalRow(PeerData *peer) {
|
||||
return createRow(App::history(peer));
|
||||
}
|
||||
|
||||
bool ChatsListBoxController::appendRow(History *history) {
|
||||
if (auto row = view()->findRow(history->peer)) {
|
||||
updateRowHook(static_cast<Row*>(row));
|
||||
return false;
|
||||
}
|
||||
if (auto row = createRow(history)) {
|
||||
view()->appendRow(std::move(row));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
namespace Ui {
|
||||
class RippleAnimation;
|
||||
class RoundImageCheckbox;
|
||||
class MultiSelect;
|
||||
template <typename Widget>
|
||||
class WidgetSlideWrap;
|
||||
@@ -40,7 +41,15 @@ public:
|
||||
public:
|
||||
Row(PeerData *peer);
|
||||
|
||||
void setDisabled(bool disabled);
|
||||
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;
|
||||
|
||||
void setActionLink(const QString &action);
|
||||
PeerData *peer() const {
|
||||
@@ -54,6 +63,7 @@ public:
|
||||
|
||||
private:
|
||||
// Inner interface.
|
||||
friend class PeerListBox;
|
||||
friend class Inner;
|
||||
|
||||
void refreshName();
|
||||
@@ -90,10 +100,25 @@ public:
|
||||
_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, int x, int y, int outerWidth, TimeMs ms);
|
||||
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;
|
||||
@@ -105,9 +130,14 @@ public:
|
||||
void lazyInitialize();
|
||||
|
||||
private:
|
||||
void createCheckbox(base::lambda<void()> updateCallback);
|
||||
void setCheckedInternal(bool checked, SetStyle style);
|
||||
void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const;
|
||||
|
||||
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;
|
||||
@@ -123,8 +153,8 @@ public:
|
||||
class Controller {
|
||||
public:
|
||||
virtual void prepare() = 0;
|
||||
virtual void rowClicked(PeerData *peer) = 0;
|
||||
virtual void rowActionClicked(PeerData *peer) {
|
||||
virtual void rowClicked(Row *row) = 0;
|
||||
virtual void rowActionClicked(Row *row) {
|
||||
}
|
||||
virtual void preloadRows() {
|
||||
}
|
||||
@@ -148,6 +178,7 @@ public:
|
||||
PeerListBox *_view = nullptr;
|
||||
|
||||
friend class PeerListBox;
|
||||
friend class Inner;
|
||||
|
||||
};
|
||||
PeerListBox(QWidget*, std::unique_ptr<Controller> controller);
|
||||
@@ -158,6 +189,7 @@ public:
|
||||
Row *findRow(PeerData *peer);
|
||||
void updateRow(Row *row);
|
||||
void removeRow(Row *row);
|
||||
void setRowChecked(Row *row, bool checked);
|
||||
int fullRowsCount() const;
|
||||
void setAboutText(const QString &aboutText);
|
||||
void setAbout(object_ptr<Ui::FlatLabel> about);
|
||||
@@ -173,10 +205,22 @@ public:
|
||||
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;
|
||||
@@ -185,6 +229,8 @@ protected:
|
||||
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();
|
||||
@@ -230,6 +276,8 @@ public:
|
||||
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());
|
||||
@@ -258,6 +306,8 @@ private:
|
||||
void refreshIndices();
|
||||
void appendGlobalSearchRow(std::unique_ptr<Row> row);
|
||||
|
||||
void invalidatePixmapsCache();
|
||||
|
||||
struct RowIndex {
|
||||
RowIndex() {
|
||||
}
|
||||
@@ -367,3 +417,33 @@ template <typename ReorderCallback>
|
||||
inline void PeerListBox::reorderRows(ReorderCallback &&callback) {
|
||||
_inner->reorderRows(std::forward<ReorderCallback>(callback));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
};
|
||||
|
Reference in New Issue
Block a user