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_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_reset" = "Terminate all 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_never" = "Never share with{count:| # user| # users}";
"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...";

View File

@ -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()) {

View File

@ -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));
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
};

View File

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

View File

@ -29,13 +29,17 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
// Release build assertions.
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);
LOG(("Assertion Failed! ") + info);
SignalHandlers::setCrashAnnotation("Assertion", info);
// Crash with access violation and generate crash report.
volatile int *t_assert_nullptr = nullptr;
*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_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.
#ifdef Expects
#undef Expects
#define Expects(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
#endif // Expects
#define Expects(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
#ifdef Ensures
#undef Ensures
#define Ensures(condition) t_assert_full(condition, "\"" #condition "\"", __FILE__, __LINE__)
#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
// QByteArray in Qt 5.3.2 doesn't declare "pointer" subtype.

View File

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

View File

@ -24,60 +24,33 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h"
#include "observer_peer.h"
#include "mainwidget.h"
#include "dialogs/dialogs_indexed_list.h"
#include "auth_session.h"
namespace Settings {
namespace {
constexpr auto kBlockedPerPage = 40;
class BlockUserBoxController : public PeerListBox::Controller, private base::Subscriber {
class BlockUserBoxController : public ChatsListBoxController {
public:
void prepare() override;
void rowClicked(PeerData *peer) override;
std::unique_ptr<PeerListBox::Row> createGlobalRow(PeerData *peer) override;
void rowClicked(PeerListBox::Row *row) 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:
void rebuildRows();
void checkForEmptyRows();
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()->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) {
if (auto user = update.peer->asUser()) {
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 {
auto blocked = user->isBlocked();
row->setDisabled(blocked);
@ -138,36 +71,23 @@ void BlockUserBoxController::updateIsBlocked(PeerListBox::Row *row, UserData *us
}
}
void BlockUserBoxController::rowClicked(PeerData *peer) {
auto user = peer->asUser();
t_assert(user != nullptr);
void BlockUserBoxController::rowClicked(PeerListBox::Row *row) {
auto user = row->peer()->asUser();
Expects(user != nullptr);
App::api()->blockUser(user);
view()->closeBox();
}
std::unique_ptr<PeerListBox::Row> BlockUserBoxController::createGlobalRow(PeerData *peer) {
if (auto user = peer->asUser()) {
return createRow(App::history(user));
std::unique_ptr<BlockUserBoxController::Row> BlockUserBoxController::createRow(History *history) {
if (auto user = history->peer->asUser()) {
auto row = std::make_unique<Row>(history);
updateIsBlocked(row.get(), user);
return 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
void BlockedBoxController::prepare() {
@ -210,7 +130,7 @@ void BlockedBoxController::preloadRows() {
_allLoaded = true;
receivedUsers(handleContactsBlocked(result.c_contacts_blocked()));
} 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) {
if (MTP::isDefaultHandledError(error)) {
@ -221,13 +141,13 @@ void BlockedBoxController::preloadRows() {
})));
}
void BlockedBoxController::rowClicked(PeerData *peer) {
Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId);
void BlockedBoxController::rowClicked(PeerListBox::Row *row) {
Ui::showPeerHistoryAsync(row->peer()->id, ShowAtUnreadMsgId);
}
void BlockedBoxController::rowActionClicked(PeerData *peer) {
auto user = peer->asUser();
t_assert(user != nullptr);
void BlockedBoxController::rowActionClicked(PeerListBox::Row *row) {
auto user = row->peer()->asUser();
Expects(user != nullptr);
App::api()->unblockUser(user);
}
@ -325,12 +245,20 @@ QString LastSeenPrivacyController::description() {
return lang(lng_edit_privacy_lastseen_description);
}
QString LastSeenPrivacyController::alwaysLinkText(int count) {
return lng_edit_privacy_lastseen_always(lt_count, count);
QString LastSeenPrivacyController::exceptionLinkText(Exception exception, int 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) {
return lng_edit_privacy_lastseen_never(lt_count, count);
QString LastSeenPrivacyController::exceptionBoxTitle(Exception exception) {
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() {

View File

@ -28,8 +28,8 @@ namespace Settings {
class BlockedBoxController : public QObject, public PeerListBox::Controller, private base::Subscriber {
public:
void prepare() override;
void rowClicked(PeerData *peer) override;
void rowActionClicked(PeerData *peer) override;
void rowClicked(PeerListBox::Row *row) override;
void rowActionClicked(PeerListBox::Row *row) override;
void preloadRows() override;
private:
@ -50,6 +50,7 @@ private:
class LastSeenPrivacyController : public EditPrivacyBox::Controller, private base::Subscriber {
public:
using Option = EditPrivacyBox::Option;
using Exception = EditPrivacyBox::Exception;
MTPInputPrivacyKey key() override;
void save(QVector<MTPInputPrivacyRule> &&result) override;
@ -57,8 +58,8 @@ public:
QString title() override;
QString optionDescription(Option option) override;
QString description() override;
QString alwaysLinkText(int count) override;
QString neverLinkText(int count) override;
QString exceptionLinkText(Exception exception, int count) override;
QString exceptionBoxTitle(Exception exception) 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")));
});
Codes.insert(qsl("crashplease"), []() {
t_assert(!"Crashed in Settings!");
Unexpected("Crashed in Settings!");
});
Codes.insert(qsl("workmode"), []() {
auto text = Global::DialogsModeEnabled() ? qsl("Disable work mode?") : qsl("Enable work mode?");

View File

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

View File

@ -101,7 +101,7 @@ QSize readGeneratedSize(const IconMask *mask, DBIScale scale) {
case dbisTwo: return QSize(width * 2, height * 2);
}
} else {
t_assert(!"Bad data in generated icon!");
Unexpected("Bad data in generated icon!");
}
}
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) {
_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) {
@ -413,6 +422,18 @@ void MultiSelect::removeItem(uint64 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) {
if (newWidth != _inner->width()) {
_inner->resizeToWidth(newWidth);
@ -572,7 +593,7 @@ void MultiSelect::Inner::paintEvent(QPaintEvent *e) {
auto checkRect = myrtlrect(paintRect);
auto paintMargins = itemPaintMargins();
for (auto i = _removingItems.begin(), e = _removingItems.end(); i != e;) {
auto item = *i;
auto &item = *i;
auto itemRect = item->paintArea(outerWidth);
itemRect = itemRect.marginsAdded(paintMargins);
if (checkRect.intersects(itemRect)) {
@ -585,7 +606,7 @@ void MultiSelect::Inner::paintEvent(QPaintEvent *e) {
++i;
}
}
for_const (auto item, _items) {
for_const (auto &item, _items) {
auto itemRect = item->paintArea(outerWidth);
itemRect = itemRect.marginsAdded(paintMargins);
if (checkRect.y() + checkRect.height() <= itemRect.y()) {
@ -615,7 +636,7 @@ void MultiSelect::Inner::mouseMoveEvent(QMouseEvent *e) {
void MultiSelect::Inner::keyPressEvent(QKeyEvent *e) {
if (_active >= 0) {
t_assert(_active < _items.size());
Expects(_active < _items.size());
if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
auto itemId = _items[_active]->id();
setActiveItemNext();
@ -643,7 +664,7 @@ void MultiSelect::Inner::onFieldFocused() {
void MultiSelect::Inner::updateSelection(QPoint mousePosition) {
auto point = myrtlpoint(mousePosition) - QPoint(_st.padding.left(), _st.padding.right());
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();
if (itemRect.y() > point.y()) {
break;
@ -674,8 +695,8 @@ void MultiSelect::Inner::updateSelection(QPoint mousePosition) {
void MultiSelect::Inner::mousePressEvent(QMouseEvent *e) {
if (_overDelete) {
t_assert(_selected >= 0);
t_assert(_selected < _items.size());
Expects(_selected >= 0);
Expects(_selected < _items.size());
removeItem(_items[_selected]->id());
} else if (_selected >= 0) {
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();
item->setUpdateCallback([this, item = item.get()] {
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());
rtlupdate(itemRect);
});
_items.push_back(item.release());
updateItemsGeometry();
_idsMap.insert(item->id());
_items.push_back(std::move(item));
if (wasEmpty) {
updateHasAnyItems(true);
}
}
void MultiSelect::Inner::finishItemsBunch(AddItemWay way) {
updateItemsGeometry();
if (way != AddItemWay::SkipAnimation) {
_items.back()->showAnimated();
} else {
@ -712,7 +737,7 @@ void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
auto itemTop = 0;
auto widthLeft = newWidth;
auto maxVisiblePadding = qMax(_st.padding.left(), _st.padding.right());
for_const (auto item, _items) {
for_const (auto &item, _items) {
auto itemWidth = item->getWidth();
t_assert(itemWidth <= newWidth);
if (itemWidth > widthLeft) {
@ -738,8 +763,6 @@ void MultiSelect::Inner::computeItemsGeometry(int newWidth) {
}
void MultiSelect::Inner::updateItemsGeometry() {
computeItemsGeometry(width());
updateFieldGeometry();
auto newHeight = resizeGetHeight(width());
if (newHeight == _newHeight) return;
@ -764,8 +787,7 @@ void MultiSelect::Inner::finishHeightAnimation() {
}
void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) {
for (int i = 0, count = _items.size(); i != count; ++i) {
auto item = _items[i];
for_const (auto &item, _items) {
if (item->id() == itemId) {
item->setText(text);
updateItemsGeometry();
@ -783,18 +805,23 @@ void MultiSelect::Inner::setResizedCallback(base::lambda<void(int heightDelta)>
}
void MultiSelect::Inner::removeItem(uint64 itemId) {
for (int i = 0, count = _items.size(); i != count; ++i) {
auto item = _items[i];
auto found = false;
for (auto i = 0, count = int(_items.size()); i != count; ++i) {
auto &item = _items[i];
if (item->id() == itemId) {
found = true;
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) {
_active = -1;
} else if (_active > i) {
--_active;
}
_removingItems.insert(item);
item->hideAnimated();
updateItemsGeometry();
if (_items.empty()) {
@ -809,19 +836,28 @@ void MultiSelect::Inner::removeItem(uint64 itemId) {
break;
}
}
if (_itemRemovedCallback) {
if (found && _itemRemovedCallback) {
_itemRemovedCallback(itemId);
}
setInnerFocus();
}
MultiSelect::Inner::~Inner() {
for (auto item : base::take(_items)) {
delete item;
}
for (auto item : base::take(_removingItems)) {
delete item;
}
int MultiSelect::Inner::getItemsCount() const {
return _items.size();
}
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

View File

@ -46,11 +46,17 @@ public:
};
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 addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage);
void finishItemsBunch();
void setItemText(uint64 itemId, const QString &text);
void setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback);
void removeItem(uint64 itemId);
int getItemsCount() const;
QVector<uint64> getItems() const;
bool hasItem(uint64 itemId) const;
protected:
int resizeGetHeight(int newWidth) override;
bool eventFilter(QObject *o, QEvent *e) override;
@ -86,15 +92,18 @@ public:
void setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback);
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 setItemRemovedCallback(base::lambda<void(uint64 itemId)> callback);
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:
int resizeGetHeight(int newWidth) override;
@ -141,10 +150,9 @@ private:
ScrollCallback _scrollCallback;
using Items = QList<Item*>;
Items _items;
using RemovingItems = OrderedSet<Item*>;
RemovingItems _removingItems;
std::set<uint64> _idsMap;
std::vector<std::unique_ptr<Item>> _items;
std::set<std::unique_ptr<Item>> _removingItems;
int _selected = -1;
int _active = -1;

View File

@ -439,19 +439,19 @@ bool Editor::Inner::readData() {
t_assert(!result.error);
_newRows->feed(name, result.color);
//if (!_newRows->feedFallbackName(name, str_const_toString(row.fallback))) {
// t_assert(!"Row for fallback not found");
// Unexpected("Row for fallback not found");
//}
} else {
auto copyOf = bytesToUtf8(row.value);
if (auto result = _existingRows->find(copyOf)) {
_newRows->feed(name, *result, 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);
}
if (!_newRows->feedDescription(name, description)) {
t_assert(!"Row for description not found");
Unexpected("Row for description not found");
}
}
}