From 3455344c62d2414e1d2ce8470b25eadcb6ce39b0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Oct 2016 22:48:35 +0300 Subject: [PATCH] Ui::MultiSelect control started: now it's just search field + cancel. --- Telegram/SourceFiles/boxes/abstractbox.cpp | 16 +- Telegram/SourceFiles/boxes/abstractbox.h | 5 +- Telegram/SourceFiles/boxes/boxes.style | 5 + Telegram/SourceFiles/boxes/contactsbox.cpp | 91 +++----- Telegram/SourceFiles/boxes/contactsbox.h | 9 +- .../SourceFiles/ui/widgets/multi_select.cpp | 198 ++++++++++++++++++ .../SourceFiles/ui/widgets/multi_select.h | 138 ++++++++++++ Telegram/SourceFiles/ui/widgets/widgets.style | 6 + Telegram/gyp/Telegram.gyp | 2 + 9 files changed, 403 insertions(+), 67 deletions(-) create mode 100644 Telegram/SourceFiles/ui/widgets/multi_select.cpp create mode 100644 Telegram/SourceFiles/ui/widgets/multi_select.h diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index e7378830a..4fcf2a68b 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -201,7 +201,7 @@ ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : Abstrac } void ScrollableBox::resizeEvent(QResizeEvent *e) { - _scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); + updateScrollGeometry(); AbstractBox::resizeEvent(e); } @@ -210,7 +210,19 @@ void ScrollableBox::init(ScrolledWidget *inner, int bottomSkip, int topSkip) { _topSkip = topSkip; _scroll->setOwnedWidget(inner); _scroll->setFocusPolicy(Qt::NoFocus); - ScrollableBox::resizeEvent(nullptr); + updateScrollGeometry(); +} + +void ScrollableBox::setScrollSkips(int bottomSkip, int topSkip) { + if (_topSkip != topSkip || _bottomSkip != bottomSkip) { + _topSkip = topSkip; + _bottomSkip = bottomSkip; + updateScrollGeometry(); + } +} + +void ScrollableBox::updateScrollGeometry() { + _scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); } ItemListBox::ItemListBox(const style::flatScroll &scroll, int32 w) : ScrollableBox(scroll, w) { diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 7368bcbc2..bd21001cc 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -106,6 +106,7 @@ public: protected: void init(ScrolledWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); + void setScrollSkips(int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); void resizeEvent(QResizeEvent *e) override; @@ -114,8 +115,10 @@ protected: } private: + void updateScrollGeometry(); + ChildWidget _scroll; - int32 _topSkip, _bottomSkip; + int _topSkip, _bottomSkip; }; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 72c27d234..3bdfe8a83 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -100,6 +100,11 @@ boxSearchCancel: IconButton { duration: 150; } +contactsMultiSelect: MultiSelect { + field: boxSearchField; + cancel: boxSearchCancel; + maxHeight: 104px; +} contactsPhotoCheckbox: RoundImageCheckbox { imageRadius: 21px; imageSmallRadius: 18px; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 41915c6a2..ad98a8315 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -30,7 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "application.h" #include "ui/filedialog.h" -#include "ui/buttons/icon_button.h" +#include "ui/widgets/multi_select.h" #include "boxes/photocropbox.h" #include "boxes/confirmbox.h" #include "observer_peer.h" @@ -42,8 +42,7 @@ QString cantInviteError() { ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll) , _inner(this, CreatingGroupNone) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) , _next(this, lang(lng_create_group_next), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) { @@ -52,8 +51,7 @@ ContactsBox::ContactsBox() : ItemListBox(st::contactsScroll) ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox(st::boxScroll) , _inner(this, CreatingGroupGroup) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) , _next(this, lang(lng_create_group_create), st::defaultBoxButton) , _cancel(this, lang(lng_create_group_back), st::cancelBoxButton) , _topShadow(this) @@ -64,8 +62,7 @@ ContactsBox::ContactsBox(const QString &name, const QImage &photo) : ItemListBox ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll) , _inner(this, channel, MembersFilter::Recent, MembersAlreadyIn()) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) , _next(this, lang(lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_create_group_skip), st::cancelBoxButton) , _topShadow(this) { @@ -74,8 +71,7 @@ ContactsBox::ContactsBox(ChannelData *channel) : ItemListBox(st::boxScroll) ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const MembersAlreadyIn &already) : ItemListBox((filter == MembersFilter::Admins) ? st::contactsScroll : st::boxScroll) , _inner(this, channel, filter, already) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) , _next(this, lang(lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) { @@ -84,8 +80,7 @@ ContactsBox::ContactsBox(ChannelData *channel, MembersFilter filter, const Membe ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st::boxScroll) , _inner(this, chat, filter) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) , _next(this, lang((filter == MembersFilter::Admins) ? lng_settings_save : lng_participant_invite), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) { @@ -94,8 +89,7 @@ ContactsBox::ContactsBox(ChatData *chat, MembersFilter filter) : ItemListBox(st: ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll) , _inner(this, bot) -, _filter(this, st::boxSearchField, lang(lng_participant_filter)) -, _filterCancel(this, st::boxSearchCancel) +, _select(this, st::contactsMultiSelect, lang(lng_participant_filter)) , _next(this, lang(lng_create_group_next), st::defaultBoxButton) , _cancel(this, lang(lng_cancel), st::cancelBoxButton) , _topShadow(this) { @@ -103,10 +97,7 @@ ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::contactsScroll) } void ContactsBox::init() { - bool inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat(); - int32 topSkip = st::boxTitleHeight + _filter->height(); - int32 bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; - ItemListBox::init(_inner, bottomSkip, topSkip); + ItemListBox::init(_inner); connect(_inner, SIGNAL(chosenChanged()), this, SLOT(onChosenChanged())); connect(_inner, SIGNAL(addRequested()), App::wnd(), SLOT(onShowAddContact())); @@ -128,16 +119,12 @@ void ContactsBox::init() { } connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); - connect(_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); - connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); + _select->setQueryChangedCallback([this](const QString &query) { onFilterUpdate(query); }); + _select->setSubmittedCallback([this](bool) { onSubmit(); }); connect(_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); - connect(_inner, SIGNAL(selectAllQuery()), _filter, SLOT(selectAll())); connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); connect(_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded())); - _filterCancel->setAttribute(Qt::WA_OpaquePaintEvent); - _searchTimer.setSingleShot(true); connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); @@ -145,7 +132,7 @@ void ContactsBox::init() { } bool ContactsBox::onSearchByUsername(bool searchCache) { - QString q = _filter->getLastText().trimmed(); + auto q = _select->getQuery(); if (q.isEmpty()) { if (_peopleRequest) { _peopleRequest = 0; @@ -213,12 +200,7 @@ bool ContactsBox::peopleFailed(const RPCError &error, mtpRequestId req) { } void ContactsBox::showAll() { - _filter->show(); - if (_filter->getLastText().isEmpty()) { - _filterCancel->hide(); - } else { - _filterCancel->show(); - } + _select->show(); if (_inner->channel() && _inner->membersFilter() == MembersFilter::Admins) { _next.hide(); _cancel.hide(); @@ -238,7 +220,7 @@ void ContactsBox::showAll() { } void ContactsBox::doSetInnerFocus() { - _filter->setFocus(); + _select->setInnerFocus(); } void ContactsBox::onSubmit() { @@ -246,7 +228,8 @@ void ContactsBox::onSubmit() { } void ContactsBox::keyPressEvent(QKeyEvent *e) { - if (_filter->hasFocus()) { + auto focused = focusWidget(); + if (_select == focused || _select->isAncestorOf(focusWidget())) { if (e->key() == Qt::Key_Down) { _inner->selectSkip(1); } else if (e->key() == Qt::Key_Up) { @@ -285,13 +268,19 @@ void ContactsBox::paintEvent(QPaintEvent *e) { void ContactsBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); - _filter->resize(width(), _filter->height()); - _filter->moveToLeft(0, st::boxTitleHeight); - _filterCancel->moveToRight(0, st::boxTitleHeight); + + _select->resizeToWidth(width()); + _select->moveToLeft(0, st::boxTitleHeight); + + auto inviting = (_inner->creating() == CreatingGroupGroup) || (_inner->channel() && _inner->membersFilter() == MembersFilter::Recent) || _inner->chat(); + auto topSkip = st::boxTitleHeight + _select->height(); + auto bottomSkip = inviting ? (st::boxButtonPadding.top() + _next.height() + st::boxButtonPadding.bottom()) : st::boxScrollSkip; + setScrollSkips(bottomSkip, topSkip); + _inner->resize(width(), _inner->height()); _next.moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _next.height()); _cancel.moveToRight(st::boxButtonPadding.right() + _next.width() + st::boxButtonPadding.left(), _next.y()); - _topShadow.setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth); + _topShadow.setGeometry(0, st::boxTitleHeight + _select->height(), width(), st::lineWidth); if (_bottomShadow) _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _next.height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); } @@ -301,18 +290,9 @@ void ContactsBox::closePressed() { } } -void ContactsBox::onFilterCancel() { - _filter->setText(QString()); -} - -void ContactsBox::onFilterUpdate() { +void ContactsBox::onFilterUpdate(const QString &filter) { scrollArea()->scrollToY(0); - if (_filter->getLastText().isEmpty()) { - _filterCancel->hide(); - } else { - _filterCancel->show(); - } - _inner->updateFilter(_filter->getLastText()); + _inner->updateFilter(filter); } void ContactsBox::onChosenChanged() { @@ -322,8 +302,7 @@ void ContactsBox::onChosenChanged() { void ContactsBox::onInvite() { QVector users(_inner->selected()); if (users.isEmpty()) { - _filter->setFocus(); - _filter->showError(); + _select->setInnerFocus(); return; } @@ -339,14 +318,12 @@ void ContactsBox::onInvite() { void ContactsBox::onCreate() { if (_saveRequestId) return; - MTPVector users(MTP_vector(_inner->selectedInputs())); - const auto &v(users.c_vector().v); - if (v.isEmpty() || (v.size() == 1 && v.at(0).type() == mtpc_inputUserSelf)) { - _filter->setFocus(); - _filter->showError(); + auto users = _inner->selectedInputs(); + if (users.isEmpty() || (users.size() == 1 && users.at(0).type() == mtpc_inputUserSelf)) { + _select->setInnerFocus(); return; } - _saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector(v), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail)); + _saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector(users), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail)); } void ContactsBox::onSaveAdmins() { @@ -493,8 +470,7 @@ bool ContactsBox::creationFail(const RPCError &error) { onClose(); return true; } else if (error.type() == "USERS_TOO_FEW") { - _filter->setFocus(); - _filter->showError(); + _select->setInnerFocus(); return true; } else if (error.type() == "PEER_FLOOD") { Ui::showLayer(new InformBox(cantInviteError()), KeepOtherLayers); @@ -1263,7 +1239,6 @@ void ContactsBox::Inner::chooseParticipant() { if (_filteredSel < 0 || _filteredSel >= _filtered.size() || contactData(_filtered[_filteredSel])->disabledChecked) return; changeCheckState(_filtered[_filteredSel]); } - emit selectAllQuery(); } } else { PeerData *peer = 0; diff --git a/Telegram/SourceFiles/boxes/contactsbox.h b/Telegram/SourceFiles/boxes/contactsbox.h index 4315a002d..8b1f519d5 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.h +++ b/Telegram/SourceFiles/boxes/contactsbox.h @@ -31,7 +31,7 @@ class IndexedList; } // namespace Dialogs namespace Ui { -class IconButton; +class MultiSelect; } // namespace Ui QString cantInviteError(); @@ -57,8 +57,6 @@ signals: void adminAdded(); public slots: - void onFilterUpdate(); - void onFilterCancel(); void onChosenChanged(); void onScroll(); @@ -82,11 +80,11 @@ protected: private: void init(); + void onFilterUpdate(const QString &filter); class Inner; ChildWidget _inner; - ChildWidget _filter; - ChildWidget _filterCancel; + ChildWidget _select; BoxButton _next, _cancel; MembersFilter _membersFilter; @@ -178,7 +176,6 @@ public: signals: void mustScrollTo(int ymin, int ymax); - void selectAllQuery(); void searchByUsername(); void chosenChanged(); void adminAdded(); diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.cpp b/Telegram/SourceFiles/ui/widgets/multi_select.cpp new file mode 100644 index 000000000..ab0888da7 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/multi_select.cpp @@ -0,0 +1,198 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/widgets/multi_select.h" + +#include "styles/style_widgets.h" +#include "ui/buttons/icon_button.h" +#include "lang.h" + +namespace Ui { + +MultiSelect::MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder) : TWidget(parent) +, _st(st) +, _scroll(this, st::boxScroll) +, _inner(this, st, placeholder) { + _scroll->setOwnedWidget(_inner); + setAttribute(Qt::WA_OpaquePaintEvent); +} + +void MultiSelect::setQueryChangedCallback(base::lambda_unique callback) { + _inner->setQueryChangedCallback(std_::move(callback)); +} + +void MultiSelect::setSubmittedCallback(base::lambda_unique callback) { + _inner->setSubmittedCallback(std_::move(callback)); +} + +void MultiSelect::setInnerFocus() { + if (_inner->setInnerFocus()) { + _scroll->scrollToY(_scroll->scrollTopMax()); + } +} + +QString MultiSelect::getQuery() const { + return _inner->getQuery(); +} + +void MultiSelect::addItem(std_::unique_ptr item) { + _inner->addItem(std_::move(item)); +} + +int MultiSelect::resizeGetHeight(int newWidth) { + _inner->resizeToWidth(newWidth); + auto newHeight = qMin(_inner->height(), _st.maxHeight); + _scroll->resize(newWidth, newHeight); + return newHeight; +} + +void MultiSelect::resizeEvent(QResizeEvent *e) { + _scroll->moveToLeft(0, 0); +} + +MultiSelect::Item::Item(uint64 id, const QString &text, const style::color &color) +: _id(id) { +} + +void MultiSelect::Item::setText(const QString &text) { + +} + +void MultiSelect::Item::paint(Painter &p, int x, int y) { + +} + +MultiSelect::Inner::Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder) : ScrolledWidget(parent) +, _st(st) +, _filter(this, _st.field, placeholder) +, _cancel(this, _st.cancel) { + connect(_filter, SIGNAL(changed()), this, SLOT(onQueryChanged())); + connect(_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmitted(bool))); + _cancel->hide(); + _cancel->setClickedCallback([this] { + _filter->setText(QString()); + _filter->setFocus(); + }); +} + +void MultiSelect::Inner::onQueryChanged() { + auto query = getQuery(); + _cancel->setVisible(!query.isEmpty()); + if (_queryChangedCallback) { + _queryChangedCallback(query); + } +} + +bool MultiSelect::Inner::setInnerFocus() { + if (!_filter->hasFocus()) { + _filter->setFocus(); + return true; + } + return false; +} + +QString MultiSelect::Inner::getQuery() const { + return _filter->getLastText().trimmed(); +} + +void MultiSelect::Inner::setQueryChangedCallback(base::lambda_unique callback) { + _queryChangedCallback = std_::move(callback); +} + +void MultiSelect::Inner::setSubmittedCallback(base::lambda_unique callback) { + _submittedCallback = std_::move(callback); +} + +int MultiSelect::Inner::resizeGetHeight(int newWidth) { + _filter->resizeToWidth(newWidth); + return _filter->height(); +} + +void MultiSelect::Inner::resizeEvent(QResizeEvent *e) { + _filter->moveToLeft(0, 0); + _cancel->moveToRight(0, 0); +} + +void MultiSelect::Inner::addItem(std_::unique_ptr item) { + _items.push_back(item.release()); + refreshItemsGeometry(nullptr); +} + +void MultiSelect::Inner::refreshItemsGeometry(Item *startingFromRowWithItem) { + int startingFromRow = 0; + int startingFromIndex = 0; + for (int row = 1, rowsCount = qMin(_rows.size(), 1); row != rowsCount; ++row) { + if (startingFromRowWithItem) { + if (_rows[row - 1].contains(startingFromRowWithItem)) { + break; + } + } + startingFromIndex += _rows[row - 1].size(); + ++startingFromRow; + } + while (_rows.size() > startingFromRow) { + _rows.pop_back(); + } + for (int i = startingFromIndex, count = _items.size(); i != count; ++i) { + Row row; + row.append(_items[i]); + _rows.append(row); + } +} + +void MultiSelect::Inner::setItemText(uint64 itemId, const QString &text) { + for (int i = 0, count = _items.size(); i != count; ++i) { + auto item = _items[i]; + if (item->id() == itemId) { + item->setText(text); + refreshItemsGeometry(item); + return; + } + } +} + +void MultiSelect::Inner::setItemRemovedCallback(base::lambda_unique callback) { + _itemRemovedCallback = std_::move(callback); +} + +void MultiSelect::Inner::removeItem(uint64 itemId) { + for (int i = 0, count = _items.size(); i != count; ++i) { + auto item = _items[i]; + if (item->id() == itemId) { + _items.removeAt(i); + refreshItemsGeometry(item); + delete item; + break; + } + } + if (_itemRemovedCallback) { + _itemRemovedCallback(itemId); + } +} + +MultiSelect::Inner::~Inner() { + base::take(_rows); + for (auto item : base::take(_items)) { + delete item; + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/multi_select.h b/Telegram/SourceFiles/ui/widgets/multi_select.h new file mode 100644 index 000000000..e44199d1e --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/multi_select.h @@ -0,0 +1,138 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "styles/style_widgets.h" + +class InputField; + +namespace Ui { + +class IconButton; + +class MultiSelect : public TWidget { +public: + MultiSelect(QWidget *parent, const style::MultiSelect &st, const QString &placeholder = QString()); + + QString getQuery() const; + void setInnerFocus(); + + void setQueryChangedCallback(base::lambda_unique callback); + void setSubmittedCallback(base::lambda_unique callback); + + class Item; + void addItem(std_::unique_ptr item); + void setItemText(uint64 itemId, const QString &text); + + void setItemRemovedCallback(base::lambda_unique callback); + void removeItem(uint64 itemId); // Always calls the itemRemovedCallback(). + +protected: + int resizeGetHeight(int newWidth) override; + + void resizeEvent(QResizeEvent *e) override; + +private: + ChildWidget _scroll; + + class Inner; + ChildWidget _inner; + + const style::MultiSelect &_st; + +}; + +class MultiSelect::Item { +public: + Item(uint64 id, const QString &text, const style::color &color); + + uint64 id() const { + return _id; + } + void setText(const QString &text); + void paint(Painter &p, int x, int y); + + virtual ~Item() = default; + +protected: + virtual void paintImage(Painter &p, int x, int y, int outerWidth, int size) = 0; + +private: + uint64 _id; + +}; + +// This class is hold in header because it requires Qt preprocessing. +class MultiSelect::Inner : public ScrolledWidget { + Q_OBJECT + +public: + Inner(QWidget *parent, const style::MultiSelect &st, const QString &placeholder); + + QString getQuery() const; + bool setInnerFocus(); + + void setQueryChangedCallback(base::lambda_unique callback); + void setSubmittedCallback(base::lambda_unique callback); + + void addItem(std_::unique_ptr item); + void setItemText(uint64 itemId, const QString &text); + + void setItemRemovedCallback(base::lambda_unique callback); + void removeItem(uint64 itemId); // Always calls the itemRemovedCallback(). + + ~Inner(); + +protected: + int resizeGetHeight(int newWidth) override; + + void resizeEvent(QResizeEvent *e) override; + +private slots: + void onQueryChanged(); + void onSubmitted(bool ctrlShiftEnter) { + if (_submittedCallback) { + _submittedCallback(ctrlShiftEnter); + } + } + +private: + void refreshItemsGeometry(Item *startingFromItem); + + const style::MultiSelect &_st; + + using Row = QList; + using Rows = QList; + Rows _rows; + + using Items = QList; + Items _items; + + ChildWidget _filter; + ChildWidget _cancel; + + base::lambda_unique _queryChangedCallback; + base::lambda_unique _submittedCallback; + base::lambda_unique _itemRemovedCallback; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 171a0089e..5db4677f0 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -67,6 +67,12 @@ RoundImageCheckbox { checkIcon: icon; } +MultiSelect { + field: InputField; + cancel: IconButton; + maxHeight: pixels; +} + widgetSlideDuration: 200; discreteSliderHeight: 39px; diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 974e779ae..f6609c10f 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -493,6 +493,8 @@ '<(src_loc)/ui/widgets/label_simple.h', '<(src_loc)/ui/widgets/media_slider.cpp', '<(src_loc)/ui/widgets/media_slider.h', + '<(src_loc)/ui/widgets/multi_select.cpp', + '<(src_loc)/ui/widgets/multi_select.h', '<(src_loc)/ui/widgets/shadow.cpp', '<(src_loc)/ui/widgets/shadow.h', '<(src_loc)/ui/widgets/widget_slide_wrap.h',