diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 43cd8c60a2..01e38917d9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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..."; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 66a00e9340..f4009e621e 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -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()) { diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index cc2d563317..f73834e834 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -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 &selected, base::lambda_once &&result)> saveCallback); + void rowClicked(PeerListBox::Row *row) override; + +protected: + void prepareViewHook() override; + std::unique_ptr createRow(History *history) override; + +private: + QString _title; + QVector _selected; + base::lambda_once &&result)> _saveCallback; + +}; + +PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(const QString &title, const QVector &selected, base::lambda_once &&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(); + 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::createRow(History *history) { + if (auto user = history->peer->asUser()) { + if (!user->isSelf()) { + return std::make_unique(history); + } + } + return std::unique_ptr(); +} + +} // 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(_controller->exceptionBoxTitle(exception), exceptionUsers(exception), base::lambda_guarded(this, [this, exception](QVector &&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(std::move(controller)), KeepOtherLayers); } -void EditPrivacyBox::editNeverUsers() { - // not implemented +QString EditPrivacyBox::exceptionLinkText(Exception exception) { + return _controller->exceptionLinkText(exception, exceptionUsers(exception).size()); } QVector EditPrivacyBox::collectResult() { @@ -169,10 +252,10 @@ QVector EditPrivacyBox::collectResult() { auto result = QVector(); result.reserve(3); - if (showAlwaysLink() && !_alwaysUsers.empty()) { + if (showExceptionLink(Exception::Always) && !_alwaysUsers.empty()) { result.push_back(MTP_inputPrivacyValueAllowUsers(MTP_vector(collectInputUsers(_alwaysUsers)))); } - if (showNeverLink() && !_neverUsers.empty()) { + if (showExceptionLink(Exception::Never) && !_neverUsers.empty()) { result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector(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 &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> &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 &widget, const QString &label) { @@ -205,8 +304,8 @@ void EditPrivacyBox::createOption(Option option, object_ptr &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(this, exceptionLinkText(exception)), exceptionLinkMargins(), [this] { + resizeGetHeight(width()); + }); + exceptionLink(exception)->entity()->setClickedCallback([this, exception] { editExceptionUsers(exception); }); }; - _alwaysLink.create(this, object_ptr(this, _controller->alwaysLinkText(_alwaysUsers.size())), exceptionLinkMargins(), linkResizedCallback); - _alwaysLink->entity()->setClickedCallback([this] { editAlwaysUsers(); }); - _neverLink.create(this, object_ptr(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)); } diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h index d356b72ca3..decded381b 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.h +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h @@ -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 &widget, const QString &label); QVector collectResult(); void loadDone(const MTPaccount_PrivacyRules &result); int countDefaultHeight(int newWidth); - void editAlwaysUsers(); - void editNeverUsers(); + + void editExceptionUsers(Exception exception); + QString exceptionLinkText(Exception exception); + QVector &exceptionUsers(Exception exception); + object_ptr> &exceptionLink(Exception exception); std::unique_ptr _controller; + Option _option = Option::Everyone; object_ptr _loading; object_ptr _everyone = { nullptr }; @@ -105,7 +112,6 @@ private: mtpRequestId _loadRequestId = 0; - Option _option = Option::Everyone; QVector _alwaysUsers; QVector _neverUsers; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index f69b89d224..af07862b24 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -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(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 searchLoading) { _inner->setSearchLoading(std::move(searchLoading)); } +QVector PeerListBox::collectSelectedRows() const { + Expects(_select != nullptr); + auto result = QVector(); + 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 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 updateCallback) { + _checkbox = std::make_unique(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) { @@ -305,7 +434,7 @@ void PeerListBox::Inner::appendRow(std::unique_ptr row) { } void PeerListBox::Inner::appendGlobalSearchRow(std::unique_ptr 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) { } } +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 @@ -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).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 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)); + return false; + } + if (auto row = createRow(history)) { + view()->appendRow(std::move(row)); + return true; + } + return false; +} diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 7a097dd1c6..ad1bb389b7 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Ui { class RippleAnimation; +class RoundImageCheckbox; class MultiSelect; template 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 + void setChecked(bool checked, SetStyle style, UpdateCallback callback) { + if (checked && !_checkbox) { + createCheckbox(std::move(callback)); + } + setCheckedInternal(checked, style); + } + void invalidatePixmapsCache(); + template 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 &nameFirstChars) { _nameFirstChars = nameFirstChars; @@ -105,9 +130,14 @@ public: void lazyInitialize(); private: + void createCheckbox(base::lambda 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 _ripple; + std::unique_ptr _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); @@ -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 about); @@ -173,10 +205,22 @@ public: void setSearchLoadingText(const QString &searchLoadingText); void setSearchLoading(object_ptr searchLoading); + template + void addSelectedRows(PeerDataRange &&range) { + Expects(_select != nullptr); + for (auto peer : range) { + addSelectItem(peer, Row::SetStyle::Fast); + } + finishSelectItemsBunch(); + } + QVector collectSelectedRows() const; + // callback takes two iterators, like [](auto &begin, auto &end). template 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> createMultiSelect(); int getTopScrollSkip() const; void updateScrollSkips(); @@ -230,6 +276,8 @@ public: void setSearchNoResults(object_ptr searchNoResults); void setSearchLoading(object_ptr searchLoading); + void changeCheckState(Row *row, bool checked, Row::SetStyle style); + template void reorderRows(ReorderCallback &&callback) { callback(_rows.begin(), _rows.end()); @@ -258,6 +306,8 @@ private: void refreshIndices(); void appendGlobalSearchRow(std::unique_ptr row); + void invalidatePixmapsCache(); + struct RowIndex { RowIndex() { } @@ -367,3 +417,33 @@ template inline void PeerListBox::reorderRows(ReorderCallback &&callback) { _inner->reorderRows(std::forward(callback)); } + +class ChatsListBoxController : public PeerListBox::Controller, protected base::Subscriber { +public: + void prepare() override final; + std::unique_ptr 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 createRow(History *history) = 0; + virtual void prepareViewHook() = 0; + virtual void updateRowHook(Row *row) { + } + +private: + void rebuildRows(); + void checkForEmptyRows(); + bool appendRow(History *history); + +}; diff --git a/Telegram/SourceFiles/core/lambda.h b/Telegram/SourceFiles/core/lambda.h index 324133fc40..3e59baa45b 100644 --- a/Telegram/SourceFiles/core/lambda.h +++ b/Telegram/SourceFiles/core/lambda.h @@ -71,14 +71,13 @@ using alignment = std::max_align_t; template constexpr bool is_large = (sizeof(std::decay_t) > 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 -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 diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index 0c27d507c5..c36027aa96 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -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. diff --git a/Telegram/SourceFiles/logs.cpp b/Telegram/SourceFiles/logs.cpp index 0efbba5bed..e05553bbd0 100644 --- a/Telegram/SourceFiles/logs.cpp +++ b/Telegram/SourceFiles/logs.cpp @@ -742,17 +742,15 @@ namespace internal { namespace internal { - struct SomeAllocatedMemoryChunk { - char data[1024 * 1024]; - }; - std::unique_ptr SomeAllocatedMemory; + using ReservedMemoryChunk = std::array; + std::unique_ptr ReservedMemory; void InstallOperatorNewHandler() { - SomeAllocatedMemory = std::make_unique(); + ReservedMemory = std::make_unique(); std::set_new_handler([] { std::set_new_handler(nullptr); - SomeAllocatedMemory.reset(); - t_assert(!"Could not allocate!"); + ReservedMemory.reset(); + Unexpected("Could not allocate!"); }); } diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index d4ed0a0e70..d36186015e 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -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 createGlobalRow(PeerData *peer) override; + void rowClicked(PeerListBox::Row *row) override; + +protected: + void prepareViewHook() override; + std::unique_ptr 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 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).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 BlockUserBoxController::createGlobalRow(PeerData *peer) { - if (auto user = peer->asUser()) { - return createRow(App::history(user)); +std::unique_ptr BlockUserBoxController::createRow(History *history) { + if (auto user = history->peer->asUser()) { + auto row = std::make_unique(history); + updateIsBlocked(row.get(), user); + return row; } return std::unique_ptr(); } -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::createRow(History *history) const { - auto row = std::make_unique(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() { diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.h b/Telegram/SourceFiles/settings/settings_privacy_controllers.h index 43ff367937..cd20c8ef4a 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.h +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.h @@ -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 &&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; }; diff --git a/Telegram/SourceFiles/settings/settings_widget.cpp b/Telegram/SourceFiles/settings/settings_widget.cpp index 34b80e569b..ce4e686904 100644 --- a/Telegram/SourceFiles/settings/settings_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_widget.cpp @@ -72,7 +72,7 @@ void fillCodes() { Ui::show(Box(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?"); diff --git a/Telegram/SourceFiles/ui/effects/widget_slide_wrap.h b/Telegram/SourceFiles/ui/effects/widget_slide_wrap.h index 215bf735ce..8eed9bf3e5 100644 --- a/Telegram/SourceFiles/ui/effects/widget_slide_wrap.h +++ b/Telegram/SourceFiles/ui/effects/widget_slide_wrap.h @@ -55,6 +55,7 @@ public: void finishAnimation() { _a_height.finish(); + myEnsureResized(_entity); animationCallback(); } diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.cpp b/Telegram/SourceFiles/ui/style/style_core_icon.cpp index ed8bdf5658..fa90d5ca72 100644 --- a/Telegram/SourceFiles/ui/style/style_core_icon.cpp +++ b/Telegram/SourceFiles/ui/style/style_core_icon.cpp @@ -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(); diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp index cf2b4793a0..60e44098ca 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.cpp +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -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(_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(_st.item, itemId, text, color, std::move(paintRoundImage))); +} + +void MultiSelect::finishItemsBunch() { + _inner->finishItemsBunch(AddItemWay::SkipAnimation); } void MultiSelect::setItemRemovedCallback(base::lambda callback) { @@ -413,6 +422,18 @@ void MultiSelect::removeItem(uint64 itemId) { _inner->removeItem(itemId); } +int MultiSelect::getItemsCount() const { + return _inner->getItemsCount(); +} + +QVector 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, AddItemWay way) { +void MultiSelect::Inner::addItemInBunch(std::unique_ptr 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, 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 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 MultiSelect::Inner::getItems() const { + auto result = QVector(); + 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 diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h index d3171b1232..1b62fe6852 100644 --- a/Telegram/SourceFiles/ui/widgets/multi_select.h +++ b/Telegram/SourceFiles/ui/widgets/multi_select.h @@ -46,11 +46,17 @@ public: }; using PaintRoundImage = base::lambda; 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 callback); void removeItem(uint64 itemId); + int getItemsCount() const; + QVector 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 callback); class Item; - void addItem(std::unique_ptr item, AddItemWay way); + void addItemInBunch(std::unique_ptr item); + void finishItemsBunch(AddItemWay way); void setItemText(uint64 itemId, const QString &text); void setItemRemovedCallback(base::lambda callback); void removeItem(uint64 itemId); - void setResizedCallback(base::lambda callback); + int getItemsCount() const; + QVector getItems() const; + bool hasItem(uint64 itemId) const; - ~Inner(); + void setResizedCallback(base::lambda callback); protected: int resizeGetHeight(int newWidth) override; @@ -141,10 +150,9 @@ private: ScrollCallback _scrollCallback; - using Items = QList; - Items _items; - using RemovingItems = OrderedSet; - RemovingItems _removingItems; + std::set _idsMap; + std::vector> _items; + std::set> _removingItems; int _selected = -1; int _active = -1; diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp index c652707cdc..7d3c38f778 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp @@ -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"); } } }