From 52a7ed77ba81c3a9b83d453c2febd88b8e73349a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Sep 2016 15:28:37 +0300 Subject: [PATCH] First version of ShareBox done, cute animations. Temporarily ShareBox is opened instead of ContactsBox, for testing. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/boxes/abstractbox.cpp | 26 +- Telegram/SourceFiles/boxes/abstractbox.h | 14 +- Telegram/SourceFiles/boxes/boxes.style | 22 + Telegram/SourceFiles/boxes/contactsbox.cpp | 23 +- Telegram/SourceFiles/boxes/sessionsbox.cpp | 4 +- Telegram/SourceFiles/boxes/sharebox.cpp | 838 +++++++++++++++++++ Telegram/SourceFiles/boxes/sharebox.h | 201 +++++ Telegram/SourceFiles/boxes/stickersetbox.cpp | 26 +- Telegram/SourceFiles/core/basic_types.h | 8 +- Telegram/SourceFiles/core/lambda_wrap.h | 39 + Telegram/SourceFiles/title.cpp | 5 +- Telegram/SourceFiles/ui/countryinput.cpp | 8 +- Telegram/Telegram.pro | 2 + Telegram/gyp/Telegram.gyp | 2 + 15 files changed, 1167 insertions(+), 54 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/sharebox.cpp create mode 100644 Telegram/SourceFiles/boxes/sharebox.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 510227e20..39229559a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -871,6 +871,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :("; "lng_reply_cant_forward" = "Sorry, no way to reply to an old message in supergroup :( Do you wish to forward it and add your comment?"; +"lng_share_title" = "Share to"; +"lng_share_confirm" = "Share"; + "lng_contact_phone" = "Phone number"; "lng_enter_contact_data" = "New Contact"; "lng_edit_group_title" = "Edit group name"; diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index f08c6fa44..7e6846c00 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -193,28 +193,32 @@ void AbstractBox::raiseShadow() { } } -ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : AbstractBox(w), -_scroll(this, scroll), _innerPtr(0), _topSkip(st::boxTitleHeight), _bottomSkip(st::boxScrollSkip) { +ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : AbstractBox(w) +, _scroll(this, scroll) +, _topSkip(st::boxTitleHeight) +, _bottomSkip(st::boxScrollSkip) { setBlueTitle(true); } void ScrollableBox::resizeEvent(QResizeEvent *e) { - _scroll.setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); + _scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); AbstractBox::resizeEvent(e); } -void ScrollableBox::init(QWidget *inner, int32 bottomSkip, int32 topSkip) { +void ScrollableBox::init(QWidget *inner, int bottomSkip, int topSkip) { _bottomSkip = bottomSkip; _topSkip = topSkip; - _innerPtr = inner; - _scroll.setWidget(_innerPtr); - _scroll.setFocusPolicy(Qt::NoFocus); - ScrollableBox::resizeEvent(0); + _scroll->setWidget(inner); + _scroll->setFocusPolicy(Qt::NoFocus); + ScrollableBox::resizeEvent(nullptr); } -void ScrollableBox::showAll() { - _scroll.show(); - AbstractBox::showAll(); +void ScrollableBox::initOwned(QWidget *inner, int bottomSkip, int topSkip) { + _bottomSkip = bottomSkip; + _topSkip = topSkip; + _scroll->setOwnedWidget(inner); + _scroll->setFocusPolicy(Qt::NoFocus); + ScrollableBox::resizeEvent(nullptr); } 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 4d604781e..5e3861c17 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -101,18 +101,20 @@ public: class ScrollableBox : public AbstractBox { public: - ScrollableBox(const style::flatScroll &scroll, int32 w = st::boxWideWidth); - void resizeEvent(QResizeEvent *e) override; + ScrollableBox(const style::flatScroll &scroll, int w = st::boxWideWidth); protected: - void init(QWidget *inner, int32 bottomSkip = st::boxScrollSkip, int32 topSkip = st::boxTitleHeight); + void init(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); + void initOwned(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); - void showAll() override; + void resizeEvent(QResizeEvent *e) override; - ScrollArea _scroll; + ScrollArea *scrollArea() { + return _scroll; + } private: - QWidget *_innerPtr; + ChildWidget _scroll; int32 _topSkip, _bottomSkip; }; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index dfeb7adff..b97752c2f 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -87,3 +87,25 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) { } localStorageBoxSkip: 10px; + +shareRowsTop: 12px; +shareRowHeight: 108px; +sharePhotoRadius: 28px; +sharePhotoSmallRadius: 24px; +sharePhotoTop: 6px; +shareSelectWidth: 2px; +shareSelectFg: windowActiveBg; +shareCheckBorder: windowBg; +shareCheckBg: windowActiveBg; +shareCheckRadius: 10px; +shareCheckSmallRadius: 3px; +shareCheckIcon: icon { + { "default_checkbox_check", windowBg, point(3px, 6px) }, +}; +shareNameFont: font(11px); +shareNameFg: windowTextFg; +shareNameActiveFg: btnYesColor; +shareNameTop: 6px; +shareColumnSkip: 6px; +shareSelectDuration: 150; +shareActivateDuration: 150; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 291ccc73a..f903768da 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -24,7 +24,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_indexed_list.h" #include "lang.h" #include "boxes/addcontactbox.h" -#include "boxes/contactsbox.h" #include "mainwidget.h" #include "mainwindow.h" #include "application.h" @@ -1353,11 +1352,11 @@ void ContactsBox::init() { _cancel.hide(); } connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + 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())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), &_scroll, SLOT(scrollToY(int, int))); + 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())); @@ -1478,9 +1477,9 @@ void ContactsBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Up) { _inner.selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(_scroll.height(), 1); + _inner.selectSkipPage(scrollArea()->height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(_scroll.height(), -1); + _inner.selectSkipPage(scrollArea()->height(), -1); } else { ItemListBox::keyPressEvent(e); } @@ -1530,7 +1529,7 @@ void ContactsBox::onFilterCancel() { } void ContactsBox::onFilterUpdate() { - _scroll.scrollToY(0); + scrollArea()->scrollToY(0); if (_filter.getLastText().isEmpty()) { _filterCancel.hide(); } else { @@ -1681,7 +1680,7 @@ bool ContactsBox::editAdminFail(const RPCError &error) { } void ContactsBox::onScroll() { - _inner.loadProfilePhotos(_scroll.scrollTop()); + _inner.loadProfilePhotos(scrollArea()->scrollTop()); } void ContactsBox::creationDone(const MTPUpdates &updates) { @@ -2245,8 +2244,8 @@ MembersBox::MembersBox(ChannelData *channel, MembersFilter filter) : ItemListBox connect(&_inner, SIGNAL(addRequested()), this, SLOT(onAdd())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), &_scroll, SLOT(scrollToY(int, int))); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); connect(&_inner, SIGNAL(loaded()), this, SLOT(onLoaded())); connect(&_loadTimer, SIGNAL(timeout()), &_inner, SLOT(load())); @@ -2260,9 +2259,9 @@ void MembersBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Up) { _inner.selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(_scroll.height(), 1); + _inner.selectSkipPage(scrollArea()->height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(_scroll.height(), -1); + _inner.selectSkipPage(scrollArea()->height(), -1); } else { ItemListBox::keyPressEvent(e); } @@ -2282,7 +2281,7 @@ void MembersBox::resizeEvent(QResizeEvent *e) { } void MembersBox::onScroll() { - _inner.loadProfilePhotos(_scroll.scrollTop()); + _inner.loadProfilePhotos(scrollArea()->scrollTop()); } void MembersBox::onAdd() { diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 03ac71a7d..b2fc879fa 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -252,10 +252,10 @@ void SessionsBox::resizeEvent(QResizeEvent *e) { void SessionsBox::showAll() { _done.show(); if (_loading) { - _scroll.hide(); + scrollArea()->hide(); _shadow.hide(); } else { - _scroll.show(); + scrollArea()->show(); _shadow.show(); } ScrollableBox::showAll(); diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp new file mode 100644 index 000000000..6e6b6ad5c --- /dev/null +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -0,0 +1,838 @@ +/* +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 "boxes/sharebox.h" + +#include "dialogs/dialogs_indexed_list.h" +#include "styles/style_boxes.h" +#include "observer_peer.h" +#include "lang.h" +#include "mainwindow.h" +#include "mainwidget.h" + +ShareBox::ShareBox(SubmitCallback &&callback) : ItemListBox(st::boxScroll) +, _callback(std_::move(callback)) +, _inner(this) +, _filter(this, st::boxSearchField, lang(lng_participant_filter)) +, _filterCancel(this, st::boxSearchCancel) +, _share(this, lang(lng_share_confirm), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _topShadow(this) +, _bottomShadow(this) { + int topSkip = st::boxTitleHeight + _filter->height(); + int bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); + init(_inner, bottomSkip, topSkip); + + connect(_inner, SIGNAL(selectedChanged()), this, SLOT(onSelectedChanged())); + connect(_share, SIGNAL(clicked()), this, SLOT(onShare())); + 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())); + connect(_inner, SIGNAL(selectAllQuery()), _filter, SLOT(selectAll())); + connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); + + _filterCancel->setAttribute(Qt::WA_OpaquePaintEvent); + + _searchTimer.setSingleShot(true); + connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); + + prepare(); +} + +bool ShareBox::onSearchByUsername(bool searchCache) { + auto query = _filter->getLastText().trimmed(); + if (query.isEmpty()) { + if (_peopleRequest) { + _peopleRequest = 0; + } + return true; + } + if (query.size() >= MinUsernameLength) { + if (searchCache) { + auto i = _peopleCache.constFind(query); + if (i != _peopleCache.cend()) { + _peopleQuery = query; + _peopleRequest = 0; + peopleReceived(i.value(), 0); + return true; + } + } else if (_peopleQuery != query) { + _peopleQuery = query; + _peopleFull = false; + _peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&ShareBox::peopleReceived), rpcFail(&ShareBox::peopleFailed)); + _peopleQueries.insert(_peopleRequest, _peopleQuery); + } + } + return false; +} + +void ShareBox::onNeedSearchByUsername() { + if (!onSearchByUsername(true)) { + _searchTimer.start(AutoSearchTimeout); + } +} + +void ShareBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId requestId) { + auto query = _peopleQuery; + + auto i = _peopleQueries.find(requestId); + if (i != _peopleQueries.cend()) { + query = i.value(); + _peopleCache[query] = result; + _peopleQueries.erase(i); + } + + if (_peopleRequest == requestId) { + switch (result.type()) { + case mtpc_contacts_found: { + auto &found = result.c_contacts_found(); + App::feedUsers(found.vusers); + App::feedChats(found.vchats); + _inner->peopleReceived(query, found.vresults.c_vector().v); + } break; + } + + _peopleRequest = 0; + onScroll(); + } +} + +bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) { + if (MTP::isDefaultHandledError(error)) return false; + + if (_peopleRequest == requestId) { + _peopleRequest = 0; + _peopleFull = true; + } + return true; +} + +void ShareBox::doSetInnerFocus() { + _filter->setFocus(); +} + +void ShareBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, lang(lng_share_title)); +} + +void ShareBox::resizeEvent(QResizeEvent *e) { + ItemListBox::resizeEvent(e); + _filter->resize(width(), _filter->height()); + _filter->moveToLeft(0, st::boxTitleHeight); + _filterCancel->moveToRight(0, st::boxTitleHeight); + _inner->resizeToWidth(width()); + moveButtons(); + _topShadow->setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth); + _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _share->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); +} + +void ShareBox::moveButtons() { + _share->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _share->height()); + auto cancelRight = st::boxButtonPadding.right(); + if (_inner->hasSelected()) { + cancelRight += _share->width() + st::boxButtonPadding.left(); + } + _cancel->moveToRight(cancelRight, _share->y()); +} + +void ShareBox::onFilterCancel() { + _filter->setText(QString()); +} + +void ShareBox::onFilterUpdate() { + scrollArea()->scrollToY(0); + _filterCancel->setVisible(!_filter->getLastText().isEmpty()); + _inner->updateFilter(_filter->getLastText()); +} + +void ShareBox::onShare() { + if (_callback) { + _callback(_inner->selected()); + } +} + +void ShareBox::onSelectedChanged() { + _share->setVisible(_inner->hasSelected()); + moveButtons(); + update(); +} + +void ShareBox::onScroll() { + auto scroll = scrollArea(); + auto scrollTop = scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); +} + +namespace internal { + +ShareInner::ShareInner(QWidget *parent) : ScrolledWidget(parent) +, _chatsIndexed(std_::make_unique(Dialogs::SortMode::Add)) { + connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); + + _rowsTop = st::shareRowsTop; + _rowHeight = st::shareRowHeight; + setAttribute(Qt::WA_OpaquePaintEvent); + + auto dialogs = App::main()->dialogsList(); + for_const (auto row, dialogs->all()) { + auto history = row->history(); + if (history->peer->canWrite()) { + _chatsIndexed->addToEnd(history); + } + } + + _filter = qsl("a"); + updateFilter(); + + prepareWideCheckIcons(); + + using UpdateFlag = Notify::PeerUpdate::Flag; + auto observeEvents = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged; + Notify::registerPeerObserver(observeEvents, this, &ShareInner::notifyPeerUpdated); +} + +void ShareInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + loadProfilePhotos(visibleTop); +} + +void ShareInner::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.flags & Notify::PeerUpdate::Flag::NameChanged) { + _chatsIndexed->peerNameChanged(update.peer, update.oldNames, update.oldNameFirstChars); + } + + updateChat(update.peer); +} + +void ShareInner::updateChat(PeerData *peer) { + auto i = _dataMap.find(peer); + if (i != _dataMap.cend()) { + updateChatName(i.value(), peer); + repaintChat(peer); + } +} + +void ShareInner::updateChatName(Chat *chat, PeerData *peer) { + chat->name.setText(st::shareNameFont, peer->name, _textNameOptions); +} + +void ShareInner::repaintChatAtIndex(int index) { + if (index < 0) return; + + auto row = index / _columnCount; + auto column = index % _columnCount; + update(rtlrect(_rowsLeft + qFloor(column * _rowWidthReal), row * _rowHeight, _rowWidth, _rowHeight, width())); +} + +ShareInner::Chat *ShareInner::getChatAtIndex(int index) { + auto row = ([this, index]() -> Dialogs::Row* { + if (index < 0) return nullptr; + if (_filter.isEmpty()) return _chatsIndexed->rowAtY(index, 1); + return (index < _filtered.size()) ? _filtered[index] : nullptr; + })(); + return row ? static_cast(row->attached) : nullptr; +} + +void ShareInner::repaintChat(PeerData *peer) { + repaintChatAtIndex(chatIndex(peer)); +} + +int ShareInner::chatIndex(PeerData *peer) const { + int index = 0; + if (_filter.isEmpty()) { + for_const (auto row, _chatsIndexed->all()) { + if (row->history()->peer == peer) { + return index; + } + ++index; + } + } else { + for_const (auto row, _filtered) { + if (row->history()->peer == peer) { + return index; + } + ++index; + } + } + return -1; +} + +void ShareInner::loadProfilePhotos(int yFrom) { + if (yFrom < 0) { + yFrom = 0; + } + if (auto part = (yFrom % _rowHeight)) { + yFrom -= part; + } + int yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5 * _columnCount; + if (!yTo) { + return; + } + yFrom *= _columnCount; + yTo *= _columnCount; + + MTP::clearLoaderPriorities(); + if (_filter.isEmpty()) { + if (!_chatsIndexed->isEmpty()) { + auto i = _chatsIndexed->cfind(yFrom, _rowHeight); + for (auto end = _chatsIndexed->cend(); i != end; ++i) { + if (((*i)->pos() * _rowHeight) >= yTo) { + break; + } + (*i)->history()->peer->loadUserpic(); + } + } + } else if (!_filtered.isEmpty()) { + int from = yFrom / _rowHeight; + if (from < 0) from = 0; + if (from < _filtered.size()) { + int to = (yTo / _rowHeight) + 1; + if (to > _filtered.size()) to = _filtered.size(); + + for (; from < to; ++from) { + _filtered[from]->history()->peer->loadUserpic(); + } + } + } +} + +ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) { + auto data = static_cast(row->attached); + if (!data) { + auto peer = row->history()->peer; + auto i = _dataMap.constFind(peer); + if (i == _dataMap.cend()) { + _dataMap.insert(peer, data = new Chat(peer)); + updateChatName(data, peer); + } else { + data = i.value(); + } + row->attached = data; + } + return data; +} + +void ShareInner::setActive(int active) { + if (active != _active) { + auto changeNameFg = [this](int index, style::color from, style::color to) { + if (auto chat = getChatAtIndex(index)) { + START_ANIMATION(chat->nameFg, func([this, chat] { + repaintChat(chat->peer); + }), from->c, to->c, st::shareActivateDuration, anim::linear); + } + }; + changeNameFg(_active, st::shareNameActiveFg, st::shareNameFg); + _active = active; + changeNameFg(_active, st::shareNameFg, st::shareNameActiveFg); + } +} + +void ShareInner::paintChat(Painter &p, Chat *chat, int index) { + auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal); + auto y = _rowsTop + (index / _columnCount) * _rowHeight; + + auto selectionLevel = chat->selection.current(chat->selected ? 1. : 0.); + + auto w = width(); + auto photoLeft = (_rowWidth - (st::sharePhotoRadius * 2)) / 2; + auto photoTop = st::sharePhotoTop; + if (chat->selection.isNull()) { + if (!chat->wideUserpicCache.isNull()) { + chat->wideUserpicCache = QPixmap(); + } + auto userpicRadius = chat->selected ? st::sharePhotoSmallRadius : st::sharePhotoRadius; + auto userpicShift = st::sharePhotoRadius - userpicRadius; + auto userpicLeft = x + photoLeft + userpicShift; + auto userpicTop = y + photoTop + userpicShift; + chat->peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, w); + } else { + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + auto userpicRadius = qRound(WideCacheScale * (st::sharePhotoRadius + (st::sharePhotoSmallRadius - st::sharePhotoRadius) * selectionLevel)); + auto userpicShift = WideCacheScale * st::sharePhotoRadius - userpicRadius; + auto userpicLeft = x + photoLeft - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift; + auto userpicTop = y + photoTop - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift; + auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2); + auto from = QRect(QPoint(0, 0), chat->wideUserpicCache.size()); + p.drawPixmapLeft(to, w, chat->wideUserpicCache, from); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + } + + if (selectionLevel > 0) { + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.setOpacity(snap(selectionLevel, 0., 1.)); + p.setBrush(Qt::NoBrush); + QPen pen = st::shareSelectFg; + pen.setWidth(st::shareSelectWidth); + p.setPen(pen); + p.drawEllipse(myrtlrect(x + photoLeft, y + photoTop, st::sharePhotoRadius * 2, st::sharePhotoRadius * 2)); + p.setOpacity(1.); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + } + + removeFadeOutedIcons(chat); + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + for (auto &icon : chat->icons) { + auto fadeIn = icon.fadeIn.current(1.); + auto fadeOut = icon.fadeOut.current(1.); + auto iconRadius = qRound(WideCacheScale * (st::shareCheckSmallRadius + fadeOut * (st::shareCheckRadius - st::shareCheckSmallRadius))); + auto iconShift = WideCacheScale * st::shareCheckRadius - iconRadius; + auto iconLeft = x + photoLeft + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift; + auto iconTop = y + photoTop + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift; + auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2); + auto from = QRect(QPoint(0, 0), _wideCheckIconCache.size()); + auto opacity = fadeIn * fadeOut; + p.setOpacity(opacity); + if (fadeOut < 1.) { + p.drawPixmapLeft(to, w, icon.wideCheckCache, from); + } else { + auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + fadeIn * 3 * st::shareCheckRadius); + p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), w, _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height())); + p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), w, _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height())); + } + } + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + p.setOpacity(1.); + + if (chat->nameFg.isNull()) { + p.setPen((index == _active) ? st::shareNameActiveFg : st::shareNameFg); + } else { + p.setPen(chat->nameFg.current()); + } + auto nameWidth = (_rowWidth - st::shareColumnSkip); + auto nameLeft = st::shareColumnSkip / 2; + auto nameTop = photoTop + st::sharePhotoRadius * 2 + st::shareNameTop; + chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, w, 2, style::al_top, 0, -1, 0, true); +} + +ShareInner::Chat::Chat(PeerData *peer) : peer(peer), name(st::sharePhotoRadius * 2) { +} + +void ShareInner::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto r = e->rect(); + p.setClipRect(r); + p.fillRect(r, st::white); + auto yFrom = r.y(), yTo = r.y() + r.height(); + auto rowFrom = yFrom / _rowHeight; + auto rowTo = (yTo + _rowHeight - 1) / _rowHeight; + auto indexFrom = rowFrom * _columnCount; + auto indexTo = rowTo * _columnCount; + if (_filter.isEmpty()) { + if (!_chatsIndexed->isEmpty() || !_byUsername.isEmpty()) { + if (!_chatsIndexed->isEmpty()) { + auto i = _chatsIndexed->cfind(indexFrom, 1); + for (auto end = _chatsIndexed->cend(); i != end; ++i) { + if (indexFrom >= indexTo) { + break; + } + paintChat(p, getChat(*i), indexFrom); + ++indexFrom; + } + indexFrom -= _chatsIndexed->size(); + indexTo -= _chatsIndexed->size(); + } + if (!_byUsername.isEmpty()) { + if (indexFrom < 0) indexFrom = 0; + while (indexFrom < indexTo) { + if (indexFrom >= d_byUsername.size()) { + break; + } + paintChat(p, d_byUsername[indexFrom], indexFrom); + ++indexFrom; + } + } + } else { + // empty + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); + } + } else { + if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { + // empty + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); + } else { + if (!_filtered.isEmpty()) { + if (indexFrom < 0) indexFrom = 0; + while (indexFrom < indexTo) { + if (indexFrom >= _filtered.size()) { + break; + } + paintChat(p, getChat(_filtered[indexFrom]), indexFrom); + ++indexFrom; + } + indexFrom -= _filtered.size(); + indexTo -= _filtered.size(); + } + if (!_byUsernameFiltered.isEmpty()) { + if (indexFrom < 0) indexFrom = 0; + while (indexFrom < indexTo) { + if (indexFrom >= d_byUsernameFiltered.size()) { + break; + } + paintChat(p, d_byUsernameFiltered[indexFrom], indexFrom); + ++indexFrom; + } + } + } + } +} + +void ShareInner::enterEvent(QEvent *e) { + setMouseTracking(true); +} + +void ShareInner::leaveEvent(QEvent *e) { + setMouseTracking(false); +} + +void ShareInner::mouseMoveEvent(QMouseEvent *e) { + updateUpon(e->pos()); + setCursor((_upon >= 0) ? style::cur_pointer : style::cur_default); +} + +void ShareInner::updateUpon(const QPoint &pos) { + auto x = pos.x(), y = pos.y(); + auto row = (y - _rowsTop) / _rowHeight; + auto column = qFloor((x - _rowsLeft) / _rowWidthReal); + auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2; + auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop; + auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip)); + auto yupon = (y >= top) && (y < top + st::sharePhotoRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2); + auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1; + auto count = _filter.isEmpty() ? (_chatsIndexed->size() + d_byUsername.size()) : (_filtered.size() + d_byUsernameFiltered.size()); + if (upon >= count) { + upon = -1; + } + _upon = upon; +} + +void ShareInner::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + updateUpon(e->pos()); + if (_upon >= 0) { + changeCheckState(getChatAtIndex(_upon)); + } + } +} + +void ShareInner::resizeEvent(QResizeEvent *e) { + _columnSkip = (width() - _columnCount * st::sharePhotoRadius * 2) / float64(_columnCount + 1); + _rowWidthReal = st::sharePhotoRadius * 2 + _columnSkip; + _rowsLeft = qFloor(_columnSkip / 2); + _rowWidth = qFloor(_rowWidthReal); + update(); +} + +struct AnimBumpy { + AnimBumpy(float64 bump) : bump(bump) + , dt0(bump - sqrt(bump * (bump - 1.))) + , k(1 / (2 * dt0 - 1)) { + } + float64 bump; + float64 dt0; + float64 k; +}; + +float64 anim_bumpy(const float64 &delta, const float64 &dt) { + static AnimBumpy data = { 1.25 }; + return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0)); +} + +void ShareInner::changeCheckState(Chat *chat) { + chat->selected = !chat->selected; + if (chat->selected) { + _selected.insert(chat->peer); + chat->icons.push_back(Chat::Icon()); + START_ANIMATION(chat->icons.back().fadeIn, func([this, chat] { + repaintChat(chat->peer); + }), 0, 1, st::shareSelectDuration, anim::linear); + } else { + _selected.remove(chat->peer); + prepareWideCheckIconCache(&chat->icons.back()); + START_ANIMATION(chat->icons.back().fadeOut, func([this, chat] { + removeFadeOutedIcons(chat); + repaintChat(chat->peer); + }), 1, 0, st::shareSelectDuration, anim::linear); + } + prepareWideUserpicCache(chat); + START_ANIMATION(chat->selection, func([this, chat] { + repaintChat(chat->peer); + }), chat->selected ? 0 : 1, chat->selected ? 1 : 0, st::shareSelectDuration, anim_bumpy); + setActive(chatIndex(chat->peer)); + emit selectedChanged(); +} + +void ShareInner::removeFadeOutedIcons(Chat *chat) { + while (!chat->icons.empty() && chat->icons.front().fadeIn.isNull() && chat->icons.front().fadeOut.isNull()) { + if (chat->icons.size() > 1 || !chat->selected) { + chat->icons.pop_front(); + } else { + break; + } + } +} + +void ShareInner::prepareWideUserpicCache(Chat *chat) { + if (chat->wideUserpicCache.isNull()) { + auto size = st::sharePhotoRadius * 2; + auto wideSize = size * WideCacheScale; + QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&cache); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + chat->peer->paintUserpic(p, size, (wideSize - size) / 2, (wideSize - size) / 2); + } + chat->wideUserpicCache = App::pixmapFromImageInPlace(std_::move(cache)); + chat->wideUserpicCache.setDevicePixelRatio(cRetinaFactor()); + } +} + +void ShareInner::prepareWideCheckIconCache(Chat::Icon *icon) { + QImage wideCache(_wideCheckCache.width(), _wideCheckCache.height(), QImage::Format_ARGB32_Premultiplied); + wideCache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&wideCache); + p.setCompositionMode(QPainter::CompositionMode_Source); + auto iconRadius = WideCacheScale * st::shareCheckRadius; + auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + icon->fadeIn.current(1.) * 3 * st::shareCheckRadius); + p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), width(), _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height())); + p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), width(), _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height())); + } + icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache)); + icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor()); +} + +void ShareInner::prepareWideCheckIcons() { + auto size = st::shareCheckRadius * 2; + auto wideSize = size * WideCacheScale; + QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&cache); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + auto pen = st::shareCheckBorder->p; + pen.setWidth(st::shareSelectWidth); + p.setPen(pen); + p.setBrush(st::shareCheckBg); + auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); + p.drawEllipse(ellipse); + } + QImage cacheIcon = cache; + { + Painter p(&cacheIcon); + auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); + st::shareCheckIcon.paint(p, ellipse.topLeft(), wideSize); + } + _wideCheckCache = App::pixmapFromImageInPlace(std_::move(cache)); + _wideCheckCache.setDevicePixelRatio(cRetinaFactor()); + _wideCheckIconCache = App::pixmapFromImageInPlace(std_::move(cacheIcon)); + _wideCheckIconCache.setDevicePixelRatio(cRetinaFactor()); +} + +bool ShareInner::hasSelected() const { + return _selected.size(); +} + +void ShareInner::updateFilter(QString filter) { + _lastQuery = filter.toLower().trimmed(); + filter = textSearchKey(filter); + + QStringList f; + if (!filter.isEmpty()) { + QStringList filterList = filter.split(cWordSplit(), QString::SkipEmptyParts); + int l = filterList.size(); + + f.reserve(l); + for (int i = 0; i < l; ++i) { + QString filterName = filterList[i].trimmed(); + if (filterName.isEmpty()) continue; + f.push_back(filterName); + } + filter = f.join(' '); + } + if (_filter != filter) { + _filter = filter; + + _byUsernameFiltered.clear(); + d_byUsernameFiltered.clear(); + for (int i = 0, l = _byUsernameDatas.size(); i < l; ++i) { + delete _byUsernameDatas[i]; + } + _byUsernameDatas.clear(); + + if (_filter.isEmpty()) { + refresh(); + } else { + QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi; + + _filtered.clear(); + if (!f.isEmpty()) { + const Dialogs::List *toFilter = nullptr; + if (!_chatsIndexed->isEmpty()) { + for (fi = fb; fi != fe; ++fi) { + auto found = _chatsIndexed->filtered(fi->at(0)); + if (found->isEmpty()) { + toFilter = nullptr; + break; + } + if (!toFilter || toFilter->size() > found->size()) { + toFilter = found; + } + } + } + if (toFilter) { + _filtered.reserve(toFilter->size()); + for_const (auto row, *toFilter) { + auto &names = row->history()->peer->names; + PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni; + for (fi = fb; fi != fe; ++fi) { + auto filterName = *fi; + for (ni = nb; ni != ne; ++ni) { + if (ni->startsWith(*fi)) { + break; + } + } + if (ni == ne) { + break; + } + } + if (fi == fe) { + _filtered.push_back(row); + } + } + } + + _byUsernameFiltered.reserve(_byUsername.size()); + d_byUsernameFiltered.reserve(d_byUsername.size()); + for (int i = 0, l = _byUsername.size(); i < l; ++i) { + auto &names = _byUsername[i]->names; + PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni; + for (fi = fb; fi != fe; ++fi) { + auto filterName = *fi; + for (ni = nb; ni != ne; ++ni) { + if (ni->startsWith(*fi)) { + break; + } + } + if (ni == ne) { + break; + } + } + if (fi == fe) { + _byUsernameFiltered.push_back(_byUsername[i]); + d_byUsernameFiltered.push_back(d_byUsername[i]); + } + } + } + refresh(); + + _searching = true; + emit searchByUsername(); + } + update(); + loadProfilePhotos(0); + } +} + +void ShareInner::peopleReceived(const QString &query, const QVector &people) { + _lastQuery = query.toLower().trimmed(); + if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1); + int32 already = _byUsernameFiltered.size(); + _byUsernameFiltered.reserve(already + people.size()); + d_byUsernameFiltered.reserve(already + people.size()); + for_const (auto &mtpPeer, people) { + auto peerId = peerFromMTP(mtpPeer); + int j = 0; + for (; j < already; ++j) { + if (_byUsernameFiltered[j]->id == peerId) break; + } + if (j == already) { + auto *peer = App::peer(peerId); + if (!peer || !peer->canWrite()) continue; + + auto chat = new Chat(peer); + _byUsernameDatas.push_back(chat); + updateChatName(chat, peer); + + _byUsernameFiltered.push_back(peer); + d_byUsernameFiltered.push_back(chat); + } + } + _searching = false; + refresh(); +} + +void ShareInner::refresh() { + if (_filter.isEmpty()) { + if (!_chatsIndexed->isEmpty() || !_byUsername.isEmpty()) { + auto count = _chatsIndexed->size() + _byUsername.size(); + auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); + resize(width(), _rowsTop + rows * _rowHeight); + } else { + resize(width(), st::noContactsHeight); + } + } else { + if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { + resize(width(), st::noContactsHeight); + } else { + auto count = _filtered.size() + _byUsernameFiltered.size(); + auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); + resize(width(), _rowsTop + rows * _rowHeight); + } + } + update(); +} + +ShareInner::~ShareInner() { + for_const (auto chat, _dataMap) { + delete chat; + } +} + +QVector ShareInner::selected() const { + QVector result; + result.reserve(_dataMap.size() + _byUsername.size()); + for_const (auto chat, _dataMap) { + if (chat->selected) { + result.push_back(chat->peer); + } + } + for (int i = 0, l = _byUsername.size(); i < l; ++i) { + if (d_byUsername[i]->selected) { + result.push_back(_byUsername[i]); + } + } + return result; +} + +} // namespace internal diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h new file mode 100644 index 000000000..a7b9820bd --- /dev/null +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -0,0 +1,201 @@ +/* +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 "abstractbox.h" +#include "core/lambda_wrap.h" +#include "core/observer.h" + +namespace Dialogs { +class Row; +class IndexedList; +} // namespace Dialogs + +namespace internal { +class ShareInner; +} // namespace internal + +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +class ShareBox : public ItemListBox, public RPCSender { + Q_OBJECT + +public: + using SubmitCallback = base::lambda_unique &)>; + ShareBox(SubmitCallback &&callback); + +private slots: + void onFilterUpdate(); + void onFilterCancel(); + void onScroll(); + + bool onSearchByUsername(bool searchCache = false); + void onNeedSearchByUsername(); + + void onShare(); + void onSelectedChanged(); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + + void doSetInnerFocus() override; + +private: + void moveButtons(); + + void peopleReceived(const MTPcontacts_Found &result, mtpRequestId requestId); + bool peopleFailed(const RPCError &error, mtpRequestId requestId); + + SubmitCallback _callback; + + ChildWidget _inner; + ChildWidget _filter; + ChildWidget _filterCancel; + + ChildWidget _share; + ChildWidget _cancel; + + ChildWidget _topShadow; + ChildWidget _bottomShadow; + + QTimer _searchTimer; + QString _peopleQuery; + bool _peopleFull = false; + mtpRequestId _peopleRequest = 0; + + using PeopleCache = QMap; + PeopleCache _peopleCache; + + using PeopleQueries = QMap; + PeopleQueries _peopleQueries; + +}; + +namespace internal { + +class ShareInner : public ScrolledWidget, public RPCSender, public Notify::Observer { + Q_OBJECT + +public: + ShareInner(QWidget *parent); + + QVector selected() const; + bool hasSelected() const; + + void peopleReceived(const QString &query, const QVector &people); + + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + void updateFilter(QString filter = QString()); + + ~ShareInner(); + +signals: + void selectAllQuery(); + void searchByUsername(); + void selectedChanged(); + +protected: + void paintEvent(QPaintEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + + static constexpr int WideCacheScale = 4; + struct Chat { + Chat(PeerData *peer); + PeerData *peer; + Text name; + bool selected = false; + QPixmap wideUserpicCache; + ColorAnimation nameFg; + FloatAnimation selection; + struct Icon { + FloatAnimation fadeIn; + FloatAnimation fadeOut; + QPixmap wideCheckCache; + }; + QList icons; + }; + void paintChat(Painter &p, Chat *chat, int index); + void updateChat(PeerData *peer); + void updateChatName(Chat *chat, PeerData *peer); + void repaintChat(PeerData *peer); + void removeFadeOutedIcons(Chat *chat); + void prepareWideUserpicCache(Chat *chat); + void prepareWideCheckIconCache(Chat::Icon *icon); + void prepareWideCheckIcons(); + int chatIndex(PeerData *peer) const; + void repaintChatAtIndex(int index); + Chat *getChatAtIndex(int index); + + void loadProfilePhotos(int yFrom); + void changeCheckState(Chat *chat); + + Chat *getChat(Dialogs::Row *row); + void setActive(int active); + void updateUpon(const QPoint &pos); + + void refresh(); + + float64 _columnSkip = 0.; + float64 _rowWidthReal = 0.; + int _rowsLeft = 0; + int _rowsTop = 0; + int _rowWidth = 0; + int _rowHeight = 0; + int _columnCount = 4; + int _active = -1; + int _upon = -1; + + std_::unique_ptr _chatsIndexed; + QString _filter; + using FilteredDialogs = QVector; + FilteredDialogs _filtered; + + QPixmap _wideCheckCache, _wideCheckIconCache; + + using DataMap = QMap; + DataMap _dataMap; + using SelectedChats = OrderedSet; + SelectedChats _selected; + + ChatData *data(Dialogs::Row *row); + + bool _searching = false; + QString _lastQuery; + using ByUsernameRows = QVector; + using ByUsernameDatas = QVector; + ByUsernameRows _byUsername, _byUsernameFiltered; + ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas + ByUsernameDatas _byUsernameDatas; + +}; + +} // namespace internal diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 29b8949cf..27523d707 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -357,7 +357,7 @@ StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st:: connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); connect(&_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); connect(&_inner, SIGNAL(installed(uint64)), this, SLOT(onInstalled(uint64))); @@ -394,7 +394,7 @@ void StickerSetBox::onUpdateButtons() { } void StickerSetBox::onScroll() { - _inner.setScrollBottom(_scroll.scrollTop() + _scroll.height()); + _inner.setScrollBottom(scrollArea()->scrollTop() + scrollArea()->height()); } void StickerSetBox::showAll() { @@ -1326,7 +1326,7 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti if (addedSet) { _inner->updateSize(); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); App::api()->requestStickerSets(); } else { _allArchivedLoaded = v.isEmpty() || (offsetId != 0); @@ -1389,7 +1389,7 @@ void StickersBox::setup() { connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int))); connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll())); connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); _scrollTimer.setSingleShot(false); rebuildList(); @@ -1404,8 +1404,8 @@ void StickersBox::onScroll() { void StickersBox::checkLoadMoreArchived() { if (_section != Section::Archived) return; - int scrollTop = _scroll.scrollTop(), scrollTopMax = _scroll.scrollTopMax(); - if (scrollTop + PreloadHeightsCount * _scroll.height() >= scrollTopMax) { + int scrollTop = scrollArea()->scrollTop(), scrollTopMax = scrollArea()->scrollTopMax(); + if (scrollTop + PreloadHeightsCount * scrollArea()->height() >= scrollTopMax) { if (!_archivedRequestId && !_allArchivedLoaded) { uint64 lastId = 0; for (auto setId = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setId != e;) { @@ -1522,7 +1522,7 @@ StickersBox::~StickersBox() { void StickersBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); _inner->resize(width(), _inner->height()); - _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); if (_topShadow) { _topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); } @@ -1546,14 +1546,14 @@ void StickersBox::onStickersUpdated() { void StickersBox::rebuildList() { _inner->rebuild(); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); } void StickersBox::onCheckDraggingScroll(int localY) { - if (localY < _scroll.scrollTop()) { - _scrollDelta = localY - _scroll.scrollTop(); - } else if (localY >= _scroll.scrollTop() + _scroll.height()) { - _scrollDelta = localY - _scroll.scrollTop() - _scroll.height() + 1; + if (localY < scrollArea()->scrollTop()) { + _scrollDelta = localY - scrollArea()->scrollTop(); + } else if (localY >= scrollArea()->scrollTop() + scrollArea()->height()) { + _scrollDelta = localY - scrollArea()->scrollTop() - scrollArea()->height() + 1; } else { _scrollDelta = 0; } @@ -1570,7 +1570,7 @@ void StickersBox::onNoDraggingScroll() { void StickersBox::onScrollTimer() { int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); - _scroll.scrollToY(_scroll.scrollTop() + d); + scrollArea()->scrollToY(scrollArea()->scrollTop() + d); } void StickersBox::onSave() { diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index 42a314387..68dd42b2e 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -1275,8 +1275,8 @@ public: template class NullFunctionImplementation : public FunctionImplementation { public: - virtual R call(Args... args) { return R(); } - virtual void destroy() {} + R call(Args... args) override { return R(); } + void destroy() override {} static NullFunctionImplementation SharedInstance; }; @@ -1325,7 +1325,7 @@ class WrappedFunction : public FunctionImplementation { public: using Method = R(*)(Args... args); WrappedFunction(Method method) : _method(method) {} - virtual R call(Args... args) { return (*_method)(args...); } + R call(Args... args) override { return (*_method)(args...); } private: Method _method; @@ -1341,7 +1341,7 @@ class ObjectFunction : public FunctionImplementation { public: using Method = R(I::*)(Args... args); ObjectFunction(O *obj, Method method) : _obj(obj), _method(method) {} - virtual R call(Args... args) { return (_obj->*_method)(args...); } + R call(Args... args) override { return (_obj->*_method)(args...); } private: O *_obj; diff --git a/Telegram/SourceFiles/core/lambda_wrap.h b/Telegram/SourceFiles/core/lambda_wrap.h index 549f35363..765c71859 100644 --- a/Telegram/SourceFiles/core/lambda_wrap.h +++ b/Telegram/SourceFiles/core/lambda_wrap.h @@ -372,3 +372,42 @@ public: }; } // namespace base + +// While we still use Function<> + +template +struct LambdaFunctionHelper; + +template +struct LambdaFunctionHelper { + using FunctionType = Function; + using UniqueType = base::lambda_unique; +}; + +template +using LambdaGetFunction = typename LambdaFunctionHelper::FunctionType; + +template +using LambdaGetUnique = typename LambdaFunctionHelper::UniqueType; + +template +class LambdaFunctionImplementation : public FunctionImplementation { +public: + LambdaFunctionImplementation(base::lambda_unique &&lambda) : _lambda(std_::move(lambda)) { + } + R call(Args... args) override { return _lambda(std_::forward(args)...); } + +private: + base::lambda_unique _lambda; + +}; + +template +inline Function lambda_wrap_helper(base::lambda_unique &&lambda) { + return Function(new LambdaFunctionImplementation(std_::move(lambda))); +} + +template ::value>> +inline LambdaGetFunction func(Lambda &&lambda) { + return lambda_wrap_helper(LambdaGetUnique(std_::move(lambda))); +} diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp index f75eb8d58..29a9e7ca6 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -142,12 +142,13 @@ void TitleWidget::setHideLevel(float64 level) { } } } - +#include "boxes/sharebox.h" // TODO void TitleWidget::onContacts() { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); if (!App::self()) return; - Ui::showLayer(new ContactsBox()); + Ui::showLayer(new ShareBox([](const QVector &result) { + })); } void TitleWidget::onAbout() { diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index 5c628fb8d..40a2c0f16 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -438,7 +438,7 @@ CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxW connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); connect(&_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(&_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), &_scroll, SLOT(scrollToY(int, int))); + connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); connect(&_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&))); _filterCancel.setAttribute(Qt::WA_OpaquePaintEvent); @@ -456,9 +456,9 @@ void CountrySelectBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Up) { _inner.selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(_scroll.height(), 1); + _inner.selectSkipPage(scrollArea()->height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(_scroll.height(), -1); + _inner.selectSkipPage(scrollArea()->height(), -1); } else { ItemListBox::keyPressEvent(e); } @@ -496,7 +496,7 @@ void CountrySelectBox::onFilterCancel() { } void CountrySelectBox::onFilterUpdate() { - _scroll.scrollToY(0); + scrollArea()->scrollToY(0); if (_filter.getLastText().isEmpty()) { _filterCancel.hide(); } else { diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index 0b1fff886..73b4b11db 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -162,6 +162,7 @@ SOURCES += \ ./SourceFiles/boxes/photosendbox.cpp \ ./SourceFiles/boxes/report_box.cpp \ ./SourceFiles/boxes/sessionsbox.cpp \ + ./SourceFiles/boxes/sharebox.cpp \ ./SourceFiles/boxes/stickersetbox.cpp \ ./SourceFiles/boxes/usernamebox.cpp \ ./SourceFiles/core/basic_types.cpp \ @@ -350,6 +351,7 @@ HEADERS += \ ./SourceFiles/boxes/photosendbox.h \ ./SourceFiles/boxes/report_box.h \ ./SourceFiles/boxes/sessionsbox.h \ + ./SourceFiles/boxes/sharebox.h \ ./SourceFiles/boxes/stickersetbox.h \ ./SourceFiles/boxes/usernamebox.h \ ./SourceFiles/core/basic_types.h \ diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 0583d2dbd..9bffcf52f 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -183,6 +183,8 @@ '<(src_loc)/boxes/report_box.h', '<(src_loc)/boxes/sessionsbox.cpp', '<(src_loc)/boxes/sessionsbox.h', + '<(src_loc)/boxes/sharebox.cpp', + '<(src_loc)/boxes/sharebox.h', '<(src_loc)/boxes/stickersetbox.cpp', '<(src_loc)/boxes/stickersetbox.h', '<(src_loc)/boxes/usernamebox.cpp',