mirror of https://github.com/procxx/kepka.git
				
				
				
			Ui::MultiSelect control started: now it's just search field + cancel.
This commit is contained in:
		
							parent
							
								
									e5a5273b3a
								
							
						
					
					
						commit
						3455344c62
					
				|  | @ -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) { | ||||
|  |  | |||
|  | @ -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<ScrollArea> _scroll; | ||||
| 	int32 _topSkip, _bottomSkip; | ||||
| 	int _topSkip, _bottomSkip; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -100,6 +100,11 @@ boxSearchCancel: IconButton { | |||
| 	duration: 150; | ||||
| } | ||||
| 
 | ||||
| contactsMultiSelect: MultiSelect { | ||||
| 	field: boxSearchField; | ||||
| 	cancel: boxSearchCancel; | ||||
| 	maxHeight: 104px; | ||||
| } | ||||
| contactsPhotoCheckbox: RoundImageCheckbox { | ||||
| 	imageRadius: 21px; | ||||
| 	imageSmallRadius: 18px; | ||||
|  |  | |||
|  | @ -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<UserData*> 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<MTPInputUser> users(MTP_vector<MTPInputUser>(_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<MTPInputUser>(v), MTP_string(_creationName)), rpcDone(&ContactsBox::creationDone), rpcFail(&ContactsBox::creationFail)); | ||||
| 	_saveRequestId = MTP::send(MTPmessages_CreateChat(MTP_vector<MTPInputUser>(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; | ||||
|  |  | |||
|  | @ -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> _inner; | ||||
| 	ChildWidget<InputField> _filter; | ||||
| 	ChildWidget<Ui::IconButton> _filterCancel; | ||||
| 	ChildWidget<Ui::MultiSelect> _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(); | ||||
|  |  | |||
|  | @ -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<void(const QString &query)> callback) { | ||||
| 	_inner->setQueryChangedCallback(std_::move(callback)); | ||||
| } | ||||
| 
 | ||||
| void MultiSelect::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> 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> 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<void(const QString &query)> callback) { | ||||
| 	_queryChangedCallback = std_::move(callback); | ||||
| } | ||||
| 
 | ||||
| void MultiSelect::Inner::setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> 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> 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<void(uint64 itemId)> 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
 | ||||
|  | @ -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<void(const QString &query)> callback); | ||||
| 	void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback); | ||||
| 
 | ||||
| 	class Item; | ||||
| 	void addItem(std_::unique_ptr<Item> item); | ||||
| 	void setItemText(uint64 itemId, const QString &text); | ||||
| 
 | ||||
| 	void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> callback); | ||||
| 	void removeItem(uint64 itemId); // Always calls the itemRemovedCallback().
 | ||||
| 
 | ||||
| protected: | ||||
| 	int resizeGetHeight(int newWidth) override; | ||||
| 
 | ||||
| 	void resizeEvent(QResizeEvent *e) override; | ||||
| 
 | ||||
| private: | ||||
| 	ChildWidget<ScrollArea> _scroll; | ||||
| 
 | ||||
| 	class Inner; | ||||
| 	ChildWidget<Inner> _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<void(const QString &query)> callback); | ||||
| 	void setSubmittedCallback(base::lambda_unique<void(bool ctrlShiftEnter)> callback); | ||||
| 
 | ||||
| 	void addItem(std_::unique_ptr<Item> item); | ||||
| 	void setItemText(uint64 itemId, const QString &text); | ||||
| 
 | ||||
| 	void setItemRemovedCallback(base::lambda_unique<void(uint64 itemId)> 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<Item*>; | ||||
| 	using Rows = QList<Row>; | ||||
| 	Rows _rows; | ||||
| 
 | ||||
| 	using Items = QList<Item*>; | ||||
| 	Items _items; | ||||
| 
 | ||||
| 	ChildWidget<InputField> _filter; | ||||
| 	ChildWidget<Ui::IconButton> _cancel; | ||||
| 
 | ||||
| 	base::lambda_unique<void(const QString &query)> _queryChangedCallback; | ||||
| 	base::lambda_unique<void(bool ctrlShiftEnter)> _submittedCallback; | ||||
| 	base::lambda_unique<void(uint64 itemId)> _itemRemovedCallback; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| } // namespace Ui
 | ||||
|  | @ -67,6 +67,12 @@ RoundImageCheckbox { | |||
| 	checkIcon: icon; | ||||
| } | ||||
| 
 | ||||
| MultiSelect { | ||||
| 	field: InputField; | ||||
| 	cancel: IconButton; | ||||
| 	maxHeight: pixels; | ||||
| } | ||||
| 
 | ||||
| widgetSlideDuration: 200; | ||||
| 
 | ||||
| discreteSliderHeight: 39px; | ||||
|  |  | |||
|  | @ -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', | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue