Select exception users in EditPrivacyBox.

This commit is contained in:
John Preston 2017-03-17 15:05:50 +03:00
parent 85fd117675
commit 61c5b45d7a
17 changed files with 622 additions and 227 deletions

View File

@ -405,7 +405,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_connection_save" = "Save"; "lng_connection_save" = "Save";
"lng_settings_blocked_users" = "Blocked users"; "lng_settings_blocked_users" = "Blocked users";
"lng_settings_last_seen_privacy" = "Last Seen privacy"; "lng_settings_last_seen_privacy" = "Last seen privacy";
"lng_settings_show_sessions" = "Show all sessions"; "lng_settings_show_sessions" = "Show all sessions";
"lng_settings_reset" = "Terminate all other sessions"; "lng_settings_reset" = "Terminate all other sessions";
"lng_settings_reset_sure" = "Are you sure you want to terminate\nall other sessions?"; "lng_settings_reset_sure" = "Are you sure you want to terminate\nall other sessions?";
@ -453,6 +453,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_edit_privacy_lastseen_always" = "Always share with{count:| # user| # users}"; "lng_edit_privacy_lastseen_always" = "Always share with{count:| # user| # users}";
"lng_edit_privacy_lastseen_never" = "Never share with{count:| # user| # users}"; "lng_edit_privacy_lastseen_never" = "Never share with{count:| # user| # users}";
"lng_edit_privacy_lastseen_exceptions" = "These settings will override the values above."; "lng_edit_privacy_lastseen_exceptions" = "These settings will override the values above.";
"lng_edit_privacy_lastseen_always_title" = "Always share with";
"lng_edit_privacy_lastseen_never_title" = "Never share with";
"lng_preview_loading" = "Getting Link Info..."; "lng_preview_loading" = "Getting Link Info...";

View File

@ -1012,7 +1012,7 @@ void ContactsBox::Inner::paintDisabledCheckUserpic(Painter &p, PeerData *peer, i
auto iconBorderPen = st::contactsPhotoCheckbox.check.border->p; auto iconBorderPen = st::contactsPhotoCheckbox.check.border->p;
iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth); iconBorderPen.setWidth(st::contactsPhotoCheckbox.selectWidth);
peer->paintUserpicLeft(p, userpicLeft, userpicTop, width(), userpicRadius * 2); peer->paintUserpicLeft(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
{ {
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
@ -1446,7 +1446,7 @@ void ContactsBox::Inner::changeCheckState(Dialogs::Row *row) {
} }
void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) { void ContactsBox::Inner::changeCheckState(ContactData *data, PeerData *peer) {
t_assert(usingMultiSelect()); Expects(usingMultiSelect());
if (isRowDisabled(peer, data)) { if (isRowDisabled(peer, data)) {
} else if (data->checkbox->checked()) { } else if (data->checkbox->checked()) {

View File

@ -25,8 +25,69 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/effects/widget_slide_wrap.h" #include "ui/effects/widget_slide_wrap.h"
#include "boxes/peer_list_box.h"
#include "lang.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 { class EditPrivacyBox::OptionWidget : public TWidget {
public: public:
OptionWidget(QWidget *parent, int value, bool selected, const QString &text, const QString &description); OptionWidget(QWidget *parent, int value, bool selected, const QString &text, const QString &description);
@ -149,12 +210,34 @@ int EditPrivacyBox::countDefaultHeight(int newWidth) {
return height; return height;
} }
void EditPrivacyBox::editAlwaysUsers() { void EditPrivacyBox::editExceptionUsers(Exception exception) {
// not implemented 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() { QString EditPrivacyBox::exceptionLinkText(Exception exception) {
// not implemented return _controller->exceptionLinkText(exception, exceptionUsers(exception).size());
} }
QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() { QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
@ -169,10 +252,10 @@ QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
auto result = QVector<MTPInputPrivacyRule>(); auto result = QVector<MTPInputPrivacyRule>();
result.reserve(3); result.reserve(3);
if (showAlwaysLink() && !_alwaysUsers.empty()) { if (showExceptionLink(Exception::Always) && !_alwaysUsers.empty()) {
result.push_back(MTP_inputPrivacyValueAllowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_alwaysUsers)))); 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)))); result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector<MTPInputUser>(collectInputUsers(_neverUsers))));
} }
switch (_option) { switch (_option) {
@ -188,12 +271,28 @@ style::margins EditPrivacyBox::exceptionLinkMargins() const {
return st::editPrivacyLinkMargin; return st::editPrivacyLinkMargin;
} }
bool EditPrivacyBox::showAlwaysLink() const { QVector<UserData*> &EditPrivacyBox::exceptionUsers(Exception exception) {
return (_option == Option::Contacts) || (_option == Option::Nobody); switch (exception) {
case Exception::Always: return _alwaysUsers;
case Exception::Never: return _neverUsers;
}
Unexpected("Invalid exception value.");
} }
bool EditPrivacyBox::showNeverLink() const { object_ptr<Ui::WidgetSlideWrap<Ui::LinkButton>> &EditPrivacyBox::exceptionLink(Exception exception) {
return (_option == Option::Everyone) || (_option == Option::Contacts); 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) { 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()] { widget->setChangedCallback([this, option, widget = widget.data()] {
if (widget->checked()) { if (widget->checked()) {
_option = option; _option = option;
_alwaysLink->toggleAnimated(showAlwaysLink()); _alwaysLink->toggleAnimated(showExceptionLink(Exception::Always));
_neverLink->toggleAnimated(showNeverLink()); _neverLink->toggleAnimated(showExceptionLink(Exception::Never));
} }
}); });
} }
@ -221,22 +320,23 @@ void EditPrivacyBox::createWidgets() {
_description.create(this, _controller->description(), Ui::FlatLabel::InitType::Simple, st::editPrivacyLabel); _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); _exceptionsTitle.create(this, lang(lng_edit_privacy_exceptions), Ui::FlatLabel::InitType::Simple, st::editPrivacyTitle);
auto linkResizedCallback = [this] { auto createExceptionLink = [this](Exception exception) {
resizeGetHeight(width()); 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); createExceptionLink(Exception::Always);
_alwaysLink->entity()->setClickedCallback([this] { editAlwaysUsers(); }); createExceptionLink(Exception::Never);
_neverLink.create(this, object_ptr<Ui::LinkButton>(this, _controller->neverLinkText(_neverUsers.size())), exceptionLinkMargins(), linkResizedCallback);
_neverLink->entity()->setClickedCallback([this] { editNeverUsers(); });
_exceptionsDescription.create(this, _controller->exceptionsDescription(), Ui::FlatLabel::InitType::Simple, st::editPrivacyLabel); _exceptionsDescription.create(this, _controller->exceptionsDescription(), Ui::FlatLabel::InitType::Simple, st::editPrivacyLabel);
addButton(lang(lng_settings_save), [this] { clearButtons();
_controller->save(collectResult()); addButton(lang(lng_settings_save), [this] { _controller->save(collectResult()); });
}); addButton(lang(lng_cancel), [this] { closeBox(); });
showChildren(); showChildren();
_alwaysLink->toggleFast(showAlwaysLink()); _alwaysLink->toggleFast(showExceptionLink(Exception::Always));
_neverLink->toggleFast(showNeverLink()); _neverLink->toggleFast(showExceptionLink(Exception::Never));
setDimensions(st::boxWideWidth, resizeGetHeight(st::boxWideWidth)); setDimensions(st::boxWideWidth, resizeGetHeight(st::boxWideWidth));
} }

View File

@ -32,10 +32,14 @@ class WidgetSlideWrap;
class EditPrivacyBox : public BoxContent { class EditPrivacyBox : public BoxContent {
public: public:
enum class Option { enum class Option {
Everyone = 0, Everyone,
Contacts, Contacts,
Nobody, Nobody,
}; };
enum class Exception {
Always,
Never,
};
class Controller { class Controller {
public: public:
@ -47,8 +51,8 @@ public:
return QString(); return QString();
} }
virtual QString description() = 0; virtual QString description() = 0;
virtual QString alwaysLinkText(int count) = 0; virtual QString exceptionLinkText(Exception exception, int count) = 0;
virtual QString neverLinkText(int count) = 0; virtual QString exceptionBoxTitle(Exception exception) = 0;
virtual QString exceptionsDescription() = 0; virtual QString exceptionsDescription() = 0;
virtual ~Controller() = default; virtual ~Controller() = default;
@ -81,17 +85,20 @@ private:
class OptionWidget; class OptionWidget;
style::margins exceptionLinkMargins() const; style::margins exceptionLinkMargins() const;
bool showAlwaysLink() const; bool showExceptionLink(Exception exception) const;
bool showNeverLink() const;
void createWidgets(); void createWidgets();
void createOption(Option option, object_ptr<OptionWidget> &widget, const QString &label); void createOption(Option option, object_ptr<OptionWidget> &widget, const QString &label);
QVector<MTPInputPrivacyRule> collectResult(); QVector<MTPInputPrivacyRule> collectResult();
void loadDone(const MTPaccount_PrivacyRules &result); void loadDone(const MTPaccount_PrivacyRules &result);
int countDefaultHeight(int newWidth); 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; std::unique_ptr<Controller> _controller;
Option _option = Option::Everyone;
object_ptr<Ui::FlatLabel> _loading; object_ptr<Ui::FlatLabel> _loading;
object_ptr<OptionWidget> _everyone = { nullptr }; object_ptr<OptionWidget> _everyone = { nullptr };
@ -105,7 +112,6 @@ private:
mtpRequestId _loadRequestId = 0; mtpRequestId _loadRequestId = 0;
Option _option = Option::Everyone;
QVector<UserData*> _alwaysUsers; QVector<UserData*> _alwaysUsers;
QVector<UserData*> _neverUsers; QVector<UserData*> _neverUsers;

View File

@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "dialogs/dialogs_indexed_list.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "auth_session.h" #include "auth_session.h"
#include "mainwidget.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/widgets/labels.h"
#include "ui/effects/widget_slide_wrap.h" #include "ui/effects/widget_slide_wrap.h"
#include "lang.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) PeerListBox::PeerListBox(QWidget*, std::unique_ptr<Controller> controller)
: _controller(std::move(controller)) { : _controller(std::move(controller)) {
@ -132,6 +136,17 @@ void PeerListBox::removeRow(Row *row) {
_inner->removeRow(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 { int PeerListBox::fullRowsCount() const {
return _inner->fullRowsCount(); return _inner->fullRowsCount();
} }
@ -158,7 +173,15 @@ void PeerListBox::setSearchMode(SearchMode mode) {
_select = createMultiSelect(); _select = createMultiSelect();
_select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { _inner->submitted(); }); _select->entity()->setSubmittedCallback([this](bool chtrlShiftEnter) { _inner->submitted(); });
_select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); }); _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); _select->moveToLeft(0, 0);
} }
if (_select) { if (_select) {
@ -190,11 +213,43 @@ void PeerListBox::setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading) {
_inner->setSearchLoading(std::move(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) { PeerListBox::Row::Row(PeerData *peer) : _peer(peer) {
} }
void PeerListBox::Row::setDisabled(bool disabled) { bool PeerListBox::Row::checked() const {
_disabled = disabled; return _checkbox && _checkbox->checked();
} }
void PeerListBox::Row::setActionLink(const QString &action) { void PeerListBox::Row::setActionLink(const QString &action) {
@ -253,6 +308,12 @@ int PeerListBox::Row::actionWidth() const {
PeerListBox::Row::~Row() = default; PeerListBox::Row::~Row() = default;
void PeerListBox::Row::invalidatePixmapsCache() {
if (_checkbox) {
_checkbox->invalidateCache();
}
}
template <typename UpdateCallback> template <typename UpdateCallback>
void PeerListBox::Row::addRipple(QSize size, QPoint point, UpdateCallback updateCallback) { void PeerListBox::Row::addRipple(QSize size, QPoint point, UpdateCallback updateCallback) {
if (!_ripple) { 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) { if (_ripple) {
_ripple->paint(p, x, y, outerWidth, ms); _ripple->paint(p, x, y, outerWidth, ms);
if (_ripple->empty()) { 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() { void PeerListBox::Row::lazyInitialize() {
if (_initialized) { if (_initialized) {
return; return;
@ -287,6 +399,17 @@ void PeerListBox::Row::lazyInitialize() {
refreshStatus(); 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) PeerListBox::Inner::Inner(QWidget *parent, Controller *controller) : TWidget(parent)
, _controller(controller) , _controller(controller)
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) { , _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(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*))); 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) { 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) { void PeerListBox::Inner::appendGlobalSearchRow(std::unique_ptr<Row> row) {
t_assert(showingSearch()); Expects(showingSearch());
if (_rowsByPeer.find(row->peer()) == _rowsByPeer.cend()) { if (_rowsByPeer.find(row->peer()) == _rowsByPeer.cend()) {
row->setAbsoluteIndex(_globalSearchRows.size()); row->setAbsoluteIndex(_globalSearchRows.size());
row->setIsGlobalSearchResult(true); 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) { void PeerListBox::Inner::addRowEntry(Row *row) {
_rowsByPeer.emplace(row->peer(), row); _rowsByPeer.emplace(row->peer(), row);
if (addingToSearchIndex()) { if (addingToSearchIndex()) {
addToSearchIndex(row); 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 { bool PeerListBox::Inner::addingToSearchIndex() const {
@ -554,11 +703,10 @@ void PeerListBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
setPressed(Selected()); setPressed(Selected());
if (e->button() == Qt::LeftButton && pressed == _selected) { if (e->button() == Qt::LeftButton && pressed == _selected) {
if (auto row = getRow(pressed.index)) { if (auto row = getRow(pressed.index)) {
auto peer = row->peer();
if (pressed.action) { if (pressed.action) {
_controller->rowActionClicked(peer); _controller->rowActionClicked(row);
} else { } 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); auto actionSelected = (selected && active.action);
p.fillRect(0, 0, width(), _rowHeight, selected ? st::contactsBgOver : st::contactsBg); p.fillRect(0, 0, width(), _rowHeight, selected ? st::contactsBgOver : st::contactsBg);
row->paintRipple(p, 0, 0, width(), ms); row->paintRipple(p, ms, 0, 0, width());
peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize); row->paintUserpic(p, ms, st::contactsPadding.left(), st::contactsPadding.top(), width());
p.setPen(st::contactsNameFg); p.setPen(st::contactsNameFg);
@ -597,6 +745,7 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) {
namew -= icon->width(); namew -= icon->width();
icon->paint(p, namex + qMin(name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, 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()); name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width());
if (actionWidth) { if (actionWidth) {
@ -885,7 +1034,7 @@ bool PeerListBox::Inner::globalSearchLoading() const {
void PeerListBox::Inner::submitted() { void PeerListBox::Inner::submitted() {
if (auto row = getRow(_selected.index)) { 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) { void PeerListBox::Inner::updateRow(RowIndex index) {
if (index.value >= 0) { if (index.value < 0) {
if (getRow(index)->disabled()) { return;
if (index == _selected.index) {
setSelected(Selected());
}
if (index == _pressed.index) {
setPressed(Selected());
}
}
update(0, getRowTop(index), width(), _rowHeight);
} }
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> template <typename Callback>
@ -1037,3 +1188,79 @@ void PeerListBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names
updateRow(row); 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;
}

View File

@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Ui { namespace Ui {
class RippleAnimation; class RippleAnimation;
class RoundImageCheckbox;
class MultiSelect; class MultiSelect;
template <typename Widget> template <typename Widget>
class WidgetSlideWrap; class WidgetSlideWrap;
@ -40,7 +41,15 @@ public:
public: public:
Row(PeerData *peer); 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); void setActionLink(const QString &action);
PeerData *peer() const { PeerData *peer() const {
@ -54,6 +63,7 @@ public:
private: private:
// Inner interface. // Inner interface.
friend class PeerListBox;
friend class Inner; friend class Inner;
void refreshName(); void refreshName();
@ -90,10 +100,25 @@ public:
_isGlobalSearchResult = 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> template <typename UpdateCallback>
void addRipple(QSize size, QPoint point, UpdateCallback updateCallback); void addRipple(QSize size, QPoint point, UpdateCallback updateCallback);
void stopLastRipple(); 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) { void setNameFirstChars(const OrderedSet<QChar> &nameFirstChars) {
_nameFirstChars = nameFirstChars; _nameFirstChars = nameFirstChars;
@ -105,9 +130,14 @@ public:
void lazyInitialize(); void lazyInitialize();
private: 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; PeerData *_peer = nullptr;
bool _initialized = false; bool _initialized = false;
std::unique_ptr<Ui::RippleAnimation> _ripple; std::unique_ptr<Ui::RippleAnimation> _ripple;
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
Text _name; Text _name;
QString _status; QString _status;
StatusType _statusType = StatusType::Online; StatusType _statusType = StatusType::Online;
@ -123,8 +153,8 @@ public:
class Controller { class Controller {
public: public:
virtual void prepare() = 0; virtual void prepare() = 0;
virtual void rowClicked(PeerData *peer) = 0; virtual void rowClicked(Row *row) = 0;
virtual void rowActionClicked(PeerData *peer) { virtual void rowActionClicked(Row *row) {
} }
virtual void preloadRows() { virtual void preloadRows() {
} }
@ -148,6 +178,7 @@ public:
PeerListBox *_view = nullptr; PeerListBox *_view = nullptr;
friend class PeerListBox; friend class PeerListBox;
friend class Inner;
}; };
PeerListBox(QWidget*, std::unique_ptr<Controller> controller); PeerListBox(QWidget*, std::unique_ptr<Controller> controller);
@ -158,6 +189,7 @@ public:
Row *findRow(PeerData *peer); Row *findRow(PeerData *peer);
void updateRow(Row *row); void updateRow(Row *row);
void removeRow(Row *row); void removeRow(Row *row);
void setRowChecked(Row *row, bool checked);
int fullRowsCount() const; int fullRowsCount() const;
void setAboutText(const QString &aboutText); void setAboutText(const QString &aboutText);
void setAbout(object_ptr<Ui::FlatLabel> about); void setAbout(object_ptr<Ui::FlatLabel> about);
@ -173,10 +205,22 @@ public:
void setSearchLoadingText(const QString &searchLoadingText); void setSearchLoadingText(const QString &searchLoadingText);
void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading); 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). // callback takes two iterators, like [](auto &begin, auto &end).
template <typename ReorderCallback> template <typename ReorderCallback>
void reorderRows(ReorderCallback &&callback); void reorderRows(ReorderCallback &&callback);
bool isRowSelected(PeerData *peer) const;
protected: protected:
void prepare() override; void prepare() override;
void setInnerFocus() override; void setInnerFocus() override;
@ -185,6 +229,8 @@ protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
private: private:
void addSelectItem(PeerData *peer, Row::SetStyle style);
void finishSelectItemsBunch();
object_ptr<Ui::WidgetSlideWrap<Ui::MultiSelect>> createMultiSelect(); object_ptr<Ui::WidgetSlideWrap<Ui::MultiSelect>> createMultiSelect();
int getTopScrollSkip() const; int getTopScrollSkip() const;
void updateScrollSkips(); void updateScrollSkips();
@ -230,6 +276,8 @@ public:
void setSearchNoResults(object_ptr<Ui::FlatLabel> searchNoResults); void setSearchNoResults(object_ptr<Ui::FlatLabel> searchNoResults);
void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading); void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading);
void changeCheckState(Row *row, bool checked, Row::SetStyle style);
template <typename ReorderCallback> template <typename ReorderCallback>
void reorderRows(ReorderCallback &&callback) { void reorderRows(ReorderCallback &&callback) {
callback(_rows.begin(), _rows.end()); callback(_rows.begin(), _rows.end());
@ -258,6 +306,8 @@ private:
void refreshIndices(); void refreshIndices();
void appendGlobalSearchRow(std::unique_ptr<Row> row); void appendGlobalSearchRow(std::unique_ptr<Row> row);
void invalidatePixmapsCache();
struct RowIndex { struct RowIndex {
RowIndex() { RowIndex() {
} }
@ -367,3 +417,33 @@ template <typename ReorderCallback>
inline void PeerListBox::reorderRows(ReorderCallback &&callback) { inline void PeerListBox::reorderRows(ReorderCallback &&callback) {
_inner->reorderRows(std::forward<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);
};

View File

@ -71,14 +71,13 @@ using alignment = std::max_align_t;
template <typename Lambda> template <typename Lambda>
constexpr bool is_large = (sizeof(std::decay_t<Lambda>) > kStorageSize); constexpr bool is_large = (sizeof(std::decay_t<Lambda>) > kStorageSize);
inline void bad_construct_copy(void *lambda, const void *source) { [[noreturn]] inline void bad_construct_copy(void *lambda, const void *source) {
t_assert(!"base::lambda bad_construct_copy() called!"); Unexpected("base::lambda bad_construct_copy() called!");
} }
template <typename Return, typename ...Args> template <typename Return, typename ...Args>
Return bad_const_call(const void *lambda, Args...) { [[noreturn]] Return bad_const_call(const void *lambda, Args...) {
t_assert(!"base::lambda bad_const_call() called!"); Unexpected("base::lambda bad_const_call() called!");
return Return();
} }
template <typename Return, typename ...Args> template <typename Return, typename ...Args>

View File

@ -29,13 +29,17 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
// Release build assertions. // Release build assertions.
inline void t_noop() { inline void t_noop() {
} }
inline void t_assert_fail(const char *message, const char *file, int32 line) { [[noreturn]] inline void t_assert_fail(const char *message, const char *file, int32 line) {
auto info = qsl("%1 %2:%3").arg(message).arg(file).arg(line); auto info = qsl("%1 %2:%3").arg(message).arg(file).arg(line);
LOG(("Assertion Failed! ") + info); LOG(("Assertion Failed! ") + info);
SignalHandlers::setCrashAnnotation("Assertion", info); SignalHandlers::setCrashAnnotation("Assertion", info);
// Crash with access violation and generate crash report.
volatile int *t_assert_nullptr = nullptr; volatile int *t_assert_nullptr = nullptr;
*t_assert_nullptr = 0; *t_assert_nullptr = 0;
// Silent the possible failure to comply noreturn warning.
std::abort();
} }
#define t_assert_full(condition, message, file, line) ((GSL_UNLIKELY(!(condition))) ? t_assert_fail(message, file, line) : t_noop()) #define t_assert_full(condition, message, file, line) ((GSL_UNLIKELY(!(condition))) ? t_assert_fail(message, file, line) : t_noop())
#define t_assert_c(condition, comment) t_assert_full(condition, "\"" #condition "\" (" comment ")", __FILE__, __LINE__) #define t_assert_c(condition, comment) t_assert_full(condition, "\"" #condition "\" (" comment ")", __FILE__, __LINE__)
@ -45,13 +49,18 @@ inline void t_assert_fail(const char *message, const char *file, int32 line) {
// Let them crash with reports and logging. // Let them crash with reports and logging.
#ifdef Expects #ifdef Expects
#undef Expects #undef Expects
#define Expects(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
#endif // Expects #endif // Expects
#define Expects(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
#ifdef Ensures #ifdef Ensures
#undef Ensures #undef Ensures
#define Ensures(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
#endif // Ensures #endif // Ensures
#define Ensures(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
#ifdef Unexpected
#undef Unexpected
#endif // Unexpected
#define Unexpected(message) t_assert_fail("Unexpected: " message, __FILE__, __LINE__)
// Define specializations for QByteArray for Qt 5.3.2, because // Define specializations for QByteArray for Qt 5.3.2, because
// QByteArray in Qt 5.3.2 doesn't declare "pointer" subtype. // QByteArray in Qt 5.3.2 doesn't declare "pointer" subtype.

View File

@ -742,17 +742,15 @@ namespace internal {
namespace internal { namespace internal {
struct SomeAllocatedMemoryChunk { using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
char data[1024 * 1024]; std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
};
std::unique_ptr<SomeAllocatedMemoryChunk> SomeAllocatedMemory;
void InstallOperatorNewHandler() { void InstallOperatorNewHandler() {
SomeAllocatedMemory = std::make_unique<SomeAllocatedMemoryChunk>(); ReservedMemory = std::make_unique<ReservedMemoryChunk>();
std::set_new_handler([] { std::set_new_handler([] {
std::set_new_handler(nullptr); std::set_new_handler(nullptr);
SomeAllocatedMemory.reset(); ReservedMemory.reset();
t_assert(!"Could not allocate!"); Unexpected("Could not allocate!");
}); });
} }

View File

@ -24,60 +24,33 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h" #include "apiwrap.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "dialogs/dialogs_indexed_list.h"
#include "auth_session.h"
namespace Settings { namespace Settings {
namespace { namespace {
constexpr auto kBlockedPerPage = 40; constexpr auto kBlockedPerPage = 40;
class BlockUserBoxController : public PeerListBox::Controller, private base::Subscriber { class BlockUserBoxController : public ChatsListBoxController {
public: public:
void prepare() override; void rowClicked(PeerListBox::Row *row) override;
void rowClicked(PeerData *peer) override;
std::unique_ptr<PeerListBox::Row> createGlobalRow(PeerData *peer) override; protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(History *history) override;
void updateRowHook(Row *row) override {
updateIsBlocked(row, row->peer()->asUser());
view()->updateRow(row);
}
private: private:
void rebuildRows();
void checkForEmptyRows();
void updateIsBlocked(PeerListBox::Row *row, UserData *user) const; void updateIsBlocked(PeerListBox::Row *row, UserData *user) const;
bool appendRow(History *history);
class Row : public PeerListBox::Row {
public:
Row(History *history) : PeerListBox::Row(history->peer), _history(history) {
}
History *history() const {
return _history;
}
private:
History *_history = nullptr;
};
std::unique_ptr<Row> createRow(History *history) const;
}; };
void BlockUserBoxController::prepare() { void BlockUserBoxController::prepareViewHook() {
view()->setTitle(lang(lng_blocked_list_add_title)); view()->setTitle(lang(lng_blocked_list_add_title));
view()->addButton(lang(lng_cancel), [this] { view()->closeBox(); }); view()->addButton(lang(lng_cancel), [this] { view()->closeBox(); });
view()->setSearchMode(PeerListBox::SearchMode::Global);
view()->setSearchNoResultsText(lang(lng_blocked_list_not_found));
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();
});
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserIsBlocked, [this](const Notify::PeerUpdate &update) { subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserIsBlocked, [this](const Notify::PeerUpdate &update) {
if (auto user = update.peer->asUser()) { if (auto user = update.peer->asUser()) {
if (auto row = view()->findRow(user)) { if (auto row = view()->findRow(user)) {
@ -88,46 +61,6 @@ void BlockUserBoxController::prepare() {
})); }));
} }
void BlockUserBoxController::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 BlockUserBoxController::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));
}
}
void BlockUserBoxController::updateIsBlocked(PeerListBox::Row *row, UserData *user) const { void BlockUserBoxController::updateIsBlocked(PeerListBox::Row *row, UserData *user) const {
auto blocked = user->isBlocked(); auto blocked = user->isBlocked();
row->setDisabled(blocked); row->setDisabled(blocked);
@ -138,36 +71,23 @@ void BlockUserBoxController::updateIsBlocked(PeerListBox::Row *row, UserData *us
} }
} }
void BlockUserBoxController::rowClicked(PeerData *peer) { void BlockUserBoxController::rowClicked(PeerListBox::Row *row) {
auto user = peer->asUser(); auto user = row->peer()->asUser();
t_assert(user != nullptr); Expects(user != nullptr);
App::api()->blockUser(user); App::api()->blockUser(user);
view()->closeBox(); view()->closeBox();
} }
std::unique_ptr<PeerListBox::Row> BlockUserBoxController::createGlobalRow(PeerData *peer) { std::unique_ptr<BlockUserBoxController::Row> BlockUserBoxController::createRow(History *history) {
if (auto user = peer->asUser()) { if (auto user = history->peer->asUser()) {
return createRow(App::history(user)); auto row = std::make_unique<Row>(history);
updateIsBlocked(row.get(), user);
return row;
} }
return std::unique_ptr<Row>(); return std::unique_ptr<Row>();
} }
bool BlockUserBoxController::appendRow(History *history) {
if (auto row = view()->findRow(history->peer)) {
updateIsBlocked(row, history->peer->asUser());
return false;
}
view()->appendRow(createRow(history));
return true;
}
std::unique_ptr<BlockUserBoxController::Row> BlockUserBoxController::createRow(History *history) const {
auto row = std::make_unique<Row>(history);
updateIsBlocked(row.get(), history->peer->asUser());
return row;
}
} // namespace } // namespace
void BlockedBoxController::prepare() { void BlockedBoxController::prepare() {
@ -210,7 +130,7 @@ void BlockedBoxController::preloadRows() {
_allLoaded = true; _allLoaded = true;
receivedUsers(handleContactsBlocked(result.c_contacts_blocked())); receivedUsers(handleContactsBlocked(result.c_contacts_blocked()));
} break; } break;
default: t_assert(!"Bad type() in MTPcontacts_GetBlocked() result."); default: Unexpected("Bad type() in MTPcontacts_GetBlocked() result.");
} }
})), rpcFail(base::lambda_guarded(this, [this](const RPCError &error) { })), rpcFail(base::lambda_guarded(this, [this](const RPCError &error) {
if (MTP::isDefaultHandledError(error)) { if (MTP::isDefaultHandledError(error)) {
@ -221,13 +141,13 @@ void BlockedBoxController::preloadRows() {
}))); })));
} }
void BlockedBoxController::rowClicked(PeerData *peer) { void BlockedBoxController::rowClicked(PeerListBox::Row *row) {
Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId); Ui::showPeerHistoryAsync(row->peer()->id, ShowAtUnreadMsgId);
} }
void BlockedBoxController::rowActionClicked(PeerData *peer) { void BlockedBoxController::rowActionClicked(PeerListBox::Row *row) {
auto user = peer->asUser(); auto user = row->peer()->asUser();
t_assert(user != nullptr); Expects(user != nullptr);
App::api()->unblockUser(user); App::api()->unblockUser(user);
} }
@ -325,12 +245,20 @@ QString LastSeenPrivacyController::description() {
return lang(lng_edit_privacy_lastseen_description); return lang(lng_edit_privacy_lastseen_description);
} }
QString LastSeenPrivacyController::alwaysLinkText(int count) { QString LastSeenPrivacyController::exceptionLinkText(Exception exception, int count) {
return lng_edit_privacy_lastseen_always(lt_count, count); switch (exception) {
case Exception::Always: return lng_edit_privacy_lastseen_always(lt_count, count);
case Exception::Never: return lng_edit_privacy_lastseen_never(lt_count, count);
}
Unexpected("Invalid exception value.");
} }
QString LastSeenPrivacyController::neverLinkText(int count) { QString LastSeenPrivacyController::exceptionBoxTitle(Exception exception) {
return lng_edit_privacy_lastseen_never(lt_count, count); switch (exception) {
case Exception::Always: return lang(lng_edit_privacy_lastseen_always_title);
case Exception::Never: return lang(lng_edit_privacy_lastseen_never_title);
}
Unexpected("Invalid exception value.");
} }
QString LastSeenPrivacyController::exceptionsDescription() { QString LastSeenPrivacyController::exceptionsDescription() {

View File

@ -28,8 +28,8 @@ namespace Settings {
class BlockedBoxController : public QObject, public PeerListBox::Controller, private base::Subscriber { class BlockedBoxController : public QObject, public PeerListBox::Controller, private base::Subscriber {
public: public:
void prepare() override; void prepare() override;
void rowClicked(PeerData *peer) override; void rowClicked(PeerListBox::Row *row) override;
void rowActionClicked(PeerData *peer) override; void rowActionClicked(PeerListBox::Row *row) override;
void preloadRows() override; void preloadRows() override;
private: private:
@ -50,6 +50,7 @@ private:
class LastSeenPrivacyController : public EditPrivacyBox::Controller, private base::Subscriber { class LastSeenPrivacyController : public EditPrivacyBox::Controller, private base::Subscriber {
public: public:
using Option = EditPrivacyBox::Option; using Option = EditPrivacyBox::Option;
using Exception = EditPrivacyBox::Exception;
MTPInputPrivacyKey key() override; MTPInputPrivacyKey key() override;
void save(QVector<MTPInputPrivacyRule> &&result) override; void save(QVector<MTPInputPrivacyRule> &&result) override;
@ -57,8 +58,8 @@ public:
QString title() override; QString title() override;
QString optionDescription(Option option) override; QString optionDescription(Option option) override;
QString description() override; QString description() override;
QString alwaysLinkText(int count) override; QString exceptionLinkText(Exception exception, int count) override;
QString neverLinkText(int count) override; QString exceptionBoxTitle(Exception exception) override;
QString exceptionsDescription() override; QString exceptionsDescription() override;
}; };

View File

@ -72,7 +72,7 @@ void fillCodes() {
Ui::show(Box<InformBox>(DebugLogging::FileLoader() ? qsl("Enabled file download logging") : qsl("Disabled file download logging"))); Ui::show(Box<InformBox>(DebugLogging::FileLoader() ? qsl("Enabled file download logging") : qsl("Disabled file download logging")));
}); });
Codes.insert(qsl("crashplease"), []() { Codes.insert(qsl("crashplease"), []() {
t_assert(!"Crashed in Settings!"); Unexpected("Crashed in Settings!");
}); });
Codes.insert(qsl("workmode"), []() { Codes.insert(qsl("workmode"), []() {
auto text = Global::DialogsModeEnabled() ? qsl("Disable work mode?") : qsl("Enable work mode?"); auto text = Global::DialogsModeEnabled() ? qsl("Disable work mode?") : qsl("Enable work mode?");

View File

@ -55,6 +55,7 @@ public:
void finishAnimation() { void finishAnimation() {
_a_height.finish(); _a_height.finish();
myEnsureResized(_entity);
animationCallback(); animationCallback();
} }

View File

@ -101,7 +101,7 @@ QSize readGeneratedSize(const IconMask *mask, DBIScale scale) {
case dbisTwo: return QSize(width * 2, height * 2); case dbisTwo: return QSize(width * 2, height * 2);
} }
} else { } else {
t_assert(!"Bad data in generated icon!"); Unexpected("Bad data in generated icon!");
} }
} }
return QSize(); return QSize();

View File

@ -402,7 +402,16 @@ QString MultiSelect::getQuery() const {
} }
void MultiSelect::addItem(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage, AddItemWay way) { void MultiSelect::addItem(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage, AddItemWay way) {
_inner->addItem(std::make_unique<Inner::Item>(_st.item, itemId, text, color, std::move(paintRoundImage)), way); addItemInBunch(itemId, text, color, std::move(paintRoundImage));
_inner->finishItemsBunch(way);
}
void MultiSelect::addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage) {
_inner->addItemInBunch(std::make_unique<Inner::Item>(_st.item, itemId, text, color, std::move(paintRoundImage)));
}
void MultiSelect::finishItemsBunch() {
_inner->finishItemsBunch(AddItemWay::SkipAnimation);
} }
void MultiSelect::setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback) { void MultiSelect::setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback) {
@ -413,6 +422,18 @@ void MultiSelect::removeItem(uint64 itemId) {
_inner->removeItem(itemId); _inner->removeItem(itemId);
} }
int MultiSelect::getItemsCount() const {
return _inner->getItemsCount();
}
QVector<uint64> MultiSelect::getItems() const {
return _inner->getItems();
}
bool MultiSelect::hasItem(uint64 itemId) const {
return _inner->hasItem(itemId);
}
int MultiSelect::resizeGetHeight(int newWidth) { int MultiSelect::resizeGetHeight(int newWidth) {
if (newWidth != _inner->width()) { if (newWidth != _inner->width()) {
_inner->resizeToWidth(newWidth); _inner->resizeToWidth(newWidth);
@ -572,7 +593,7 @@ void MultiSelect::Inner::paintEvent(QPaintEvent *e) {
auto checkRect = myrtlrect(paintRect); auto checkRect = myrtlrect(paintRect);
auto paintMargins = itemPaintMargins(); auto paintMargins = itemPaintMargins();
for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) { for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) {
auto item = *i; auto &item = *i;
auto itemRect = item->paintArea(outerWidth); auto itemRect = item->paintArea(outerWidth);
itemRect = itemRect.marginsAdded(paintMargins); itemRect = itemRect.marginsAdded(paintMargins);
if (checkRect.intersects(itemRect)) { if (checkRect.intersects(itemRect)) {
@ -585,7 +606,7 @@ void MultiSelect::Inner::paintEvent(QPaintEvent *e) {
++i; ++i;
} }
} }
for_const (auto item, _items) { for_const (auto &item, _items) {
auto itemRect = item->paintArea(outerWidth); auto itemRect = item->paintArea(outerWidth);
itemRect = itemRect.marginsAdded(paintMargins); itemRect = itemRect.marginsAdded(paintMargins);
if (checkRect.y() + checkRect.height() <= itemRect.y()) { if (checkRect.y() + checkRect.height() <= itemRect.y()) {
@ -615,7 +636,7 @@ void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) {
void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) { void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) {
if (_active >= 0) { if (_active >= 0) {
t_assert(_active < _items.size()); Expects(_active < _items.size());
if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
auto itemId = _items[_active]->id(); auto itemId = _items[_active]->id();
setActiveItemNext(); setActiveItemNext();
@ -643,7 +664,7 @@ void MultiSelect::Inner::onFieldFocused() {
void MultiSelect::Inner::updateSelection(QPoint mousePosition) { void MultiSelect::Inner::updateSelection(QPoint mousePosition) {
auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right()); auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right());
auto selected = -1; auto selected = -1;
for (auto i = 0, size = _items.size(); i != size; ++i) { for (auto i = 0, count = int(_items.size()); i != count; ++i) {
auto itemRect = _items[i]->rect(); auto itemRect = _items[i]->rect();
if (itemRect.y() > point.y()) { if (itemRect.y() > point.y()) {
break; break;
@ -674,8 +695,8 @@ void MultiSelect::Inner::updateSelection(QPoint mousePosition) {
void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) { void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) {
if (_overDelete) { if (_overDelete) {
t_assert(_selected >= 0); Expects(_selected >= 0);
t_assert(_selected < _items.size()); Expects(_selected < _items.size());
removeItem(_items[_selected]->id()); removeItem(_items[_selected]->id());
} else if (_selected >= 0) { } else if (_selected >= 0) {
setActiveItem(_selected); setActiveItem(_selected);
@ -684,7 +705,7 @@ void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) {
} }
} }
void MultiSelect::Inner::addItem(std::unique_ptr<Item> item, AddItemWay way) { void MultiSelect::Inner::addItemInBunch(std::unique_ptr<Item> item) {
auto wasEmpty = _items.empty(); auto wasEmpty = _items.empty();
item->setUpdateCallback([this, item = item.get()] { item->setUpdateCallback([this, item = item.get()] {
auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top()); auto itemRect = item->paintArea(width() - _st.padding.left() - _st.padding.top());
@ -692,11 +713,15 @@ void MultiSelect::Inner::addItem(std::unique_ptr<Item> item, AddItemWay way) {
itemRect = itemRect.marginsAdded(itemPaintMargins()); itemRect = itemRect.marginsAdded(itemPaintMargins());
rtlupdate(itemRect); rtlupdate(itemRect);
}); });
_items.push_back(item.release()); _idsMap.insert(item->id());
updateItemsGeometry(); _items.push_back(std::move(item));
if (wasEmpty) { if (wasEmpty) {
updateHasAnyItems(true); updateHasAnyItems(true);
} }
}
void MultiSelect::Inner::finishItemsBunch(AddItemWay way) {
updateItemsGeometry();
if (way != AddItemWay::SkipAnimation) { if (way != AddItemWay::SkipAnimation) {
_items.back()->showAnimated(); _items.back()->showAnimated();
} else { } else {
@ -712,7 +737,7 @@ void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
auto itemTop = 0; auto itemTop = 0;
auto widthLeft = newWidth; auto widthLeft = newWidth;
auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right()); auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right());
for_const (auto item, _items) { for_const (auto &item, _items) {
auto itemWidth = item->getWidth(); auto itemWidth = item->getWidth();
t_assert(itemWidth <= newWidth); t_assert(itemWidth <= newWidth);
if (itemWidth > widthLeft) { if (itemWidth > widthLeft) {
@ -738,8 +763,6 @@ void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
} }
void MultiSelect::Inner::updateItemsGeometry() { void MultiSelect::Inner::updateItemsGeometry() {
computeItemsGeometry(width());
updateFieldGeometry();
auto newHeight = resizeGetHeight(width()); auto newHeight = resizeGetHeight(width());
if (newHeight == _newHeight) return; if (newHeight == _newHeight) return;
@ -764,8 +787,7 @@ void MultiSelect::Inner::finishHeightAnimation() {
} }
void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) { void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
for (int i = 0, count = _items.size(); i != count; ++i) { for_const (auto &item, _items) {
auto item = _items[i];
if (item->id() == itemId) { if (item->id() == itemId) {
item->setText(text); item->setText(text);
updateItemsGeometry(); updateItemsGeometry();
@ -783,18 +805,23 @@ void MultiSelect::Inner::setResizedCallback(base::lambda<void(int heightDelta)>
} }
void MultiSelect::Inner::removeItem(uint64 itemId) { void MultiSelect::Inner::removeItem(uint64 itemId) {
for (int i = 0, count = _items.size(); i != count; ++i) { auto found = false;
auto item = _items[i]; for (auto i = 0, count = int(_items.size()); i != count; ++i) {
auto &item = _items[i];
if (item->id() == itemId) { if (item->id() == itemId) {
found = true;
clearSelection(); clearSelection();
_items.removeAt(i);
item->hideAnimated();
_idsMap.erase(item->id());
auto inserted = _removingItems.insert(std::move(item));
_items.erase(_items.begin() + i);
if (_active == i) { if (_active == i) {
_active = -1; _active = -1;
} else if (_active > i) { } else if (_active > i) {
--_active; --_active;
} }
_removingItems.insert(item);
item->hideAnimated();
updateItemsGeometry(); updateItemsGeometry();
if (_items.empty()) { if (_items.empty()) {
@ -809,19 +836,28 @@ void MultiSelect::Inner::removeItem(uint64 itemId) {
break; break;
} }
} }
if (_itemRemovedCallback) { if (found && _itemRemovedCallback) {
_itemRemovedCallback(itemId); _itemRemovedCallback(itemId);
} }
setInnerFocus(); setInnerFocus();
} }
MultiSelect::Inner::~Inner() { int MultiSelect::Inner::getItemsCount() const {
for (auto item : base::take(_items)) { return _items.size();
delete item;
}
for (auto item : base::take(_removingItems)) {
delete item;
}
} }
QVector<uint64> MultiSelect::Inner::getItems() const {
auto result = QVector<uint64>();
result.reserve(_items.size());
for_const (auto &item, _items) {
result.push_back(item->id());
}
return result;
}
bool MultiSelect::Inner::hasItem(uint64 itemId) const {
return _idsMap.find(itemId) != _idsMap.cend();
}
} // namespace Ui } // namespace Ui

View File

@ -46,11 +46,17 @@ public:
}; };
using PaintRoundImage = base::lambda<void(Painter &p, int x, int y, int outerWidth, int size)>; using PaintRoundImage = base::lambda<void(Painter &p, int x, int y, int outerWidth, int size)>;
void addItem(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage, AddItemWay way = AddItemWay::Default); void addItem(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage, AddItemWay way = AddItemWay::Default);
void addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage);
void finishItemsBunch();
void setItemText(uint64 itemId, const QString &text); void setItemText(uint64 itemId, const QString &text);
void setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback); void setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback);
void removeItem(uint64 itemId); void removeItem(uint64 itemId);
int getItemsCount() const;
QVector<uint64> getItems() const;
bool hasItem(uint64 itemId) const;
protected: protected:
int resizeGetHeight(int newWidth) override; int resizeGetHeight(int newWidth) override;
bool eventFilter(QObject *o, QEvent *e) override; bool eventFilter(QObject *o, QEvent *e) override;
@ -86,15 +92,18 @@ public:
void setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback); void setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback);
class Item; class Item;
void addItem(std::unique_ptr<Item> item, AddItemWay way); void addItemInBunch(std::unique_ptr<Item> item);
void finishItemsBunch(AddItemWay way);
void setItemText(uint64 itemId, const QString &text); void setItemText(uint64 itemId, const QString &text);
void setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback); void setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback);
void removeItem(uint64 itemId); void removeItem(uint64 itemId);
void setResizedCallback(base::lambda<void(int heightDelta)> callback); int getItemsCount() const;
QVector<uint64> getItems() const;
bool hasItem(uint64 itemId) const;
~Inner(); void setResizedCallback(base::lambda<void(int heightDelta)> callback);
protected: protected:
int resizeGetHeight(int newWidth) override; int resizeGetHeight(int newWidth) override;
@ -141,10 +150,9 @@ private:
ScrollCallback _scrollCallback; ScrollCallback _scrollCallback;
using Items = QList<Item*>; std::set<uint64> _idsMap;
Items _items; std::vector<std::unique_ptr<Item>> _items;
using RemovingItems = OrderedSet<Item*>; std::set<std::unique_ptr<Item>> _removingItems;
RemovingItems _removingItems;
int _selected = -1; int _selected = -1;
int _active = -1; int _active = -1;

View File

@ -439,19 +439,19 @@ bool Editor::Inner::readData() {
t_assert(!result.error); t_assert(!result.error);
_newRows->feed(name, result.color); _newRows->feed(name, result.color);
//if (!_newRows->feedFallbackName(name, str_const_toString(row.fallback))) { //if (!_newRows->feedFallbackName(name, str_const_toString(row.fallback))) {
// t_assert(!"Row for fallback not found"); // Unexpected("Row for fallback not found");
//} //}
} else { } else {
auto copyOf = bytesToUtf8(row.value); auto copyOf = bytesToUtf8(row.value);
if (auto result = _existingRows->find(copyOf)) { if (auto result = _existingRows->find(copyOf)) {
_newRows->feed(name, *result, copyOf); _newRows->feed(name, *result, copyOf);
} else if (!_newRows->feedCopy(name, copyOf)) { } else if (!_newRows->feedCopy(name, copyOf)) {
t_assert(!"Copy of unknown value in the default palette"); Unexpected("Copy of unknown value in the default palette");
} }
t_assert(row.fallback.size() == 0); t_assert(row.fallback.size() == 0);
} }
if (!_newRows->feedDescription(name, description)) { if (!_newRows->feedDescription(name, description)) {
t_assert(!"Row for description not found"); Unexpected("Row for description not found");
} }
} }
} }