mirror of
https://github.com/vale981/tdesktop
synced 2025-03-05 09:41:41 -05:00
Search for messages from a specific user in group.
This commit is contained in:
parent
8c60ac78aa
commit
6f746c238a
14 changed files with 485 additions and 163 deletions
|
@ -1188,6 +1188,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
"lng_search_found_results#one" = "Found {count} message";
|
||||
"lng_search_found_results#other" = "Found {count} messages";
|
||||
"lng_search_global_results" = "Global search results";
|
||||
"lng_search_messages_from" = "Show messages from";
|
||||
|
||||
"lng_media_save_progress" = "{ready} of {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Save As...";
|
||||
|
|
|
@ -301,25 +301,7 @@ contactsMultiSelect: MultiSelect {
|
|||
hiding: 1000;
|
||||
}
|
||||
|
||||
item: MultiSelectItem {
|
||||
padding: margins(6px, 7px, 12px, 0px);
|
||||
maxWidth: 128px;
|
||||
height: 32px;
|
||||
style: defaultTextStyle;
|
||||
textBg: contactsBgOver;
|
||||
textFg: windowFg;
|
||||
textActiveBg: activeButtonBg;
|
||||
textActiveFg: activeButtonFg;
|
||||
deleteFg: activeButtonFg;
|
||||
deleteCross: CrossAnimation {
|
||||
size: 32px;
|
||||
skip: 10px;
|
||||
stroke: 2px;
|
||||
minScale: 0.3;
|
||||
}
|
||||
duration: 150;
|
||||
minScale: 0.3;
|
||||
}
|
||||
item: defaultMultiSelectItem;
|
||||
itemSkip: 8px;
|
||||
|
||||
field: contactsSearchField;
|
||||
|
|
|
@ -112,6 +112,10 @@ dialogsSearchFrom: IconButton(dialogsCalendar) {
|
|||
icon: icon {{ "dialogs_search_from", dialogsMenuIconFg }};
|
||||
iconOver: icon {{ "dialogs_search_from", dialogsMenuIconFgOver }};
|
||||
}
|
||||
dialogsSearchFromPadding: margins(10px, 10px, 10px, 10px);
|
||||
dialogsSearchFromBubble: MultiSelectItem(defaultMultiSelectItem) {
|
||||
maxWidth: 240px;
|
||||
}
|
||||
|
||||
dialogsFilter: FlatInput(defaultFlatInput) {
|
||||
font: font(fsize);
|
||||
|
|
|
@ -22,8 +22,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "boxes/contacts_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "data/data_drafts.h"
|
||||
|
@ -37,6 +39,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "auth_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -45,6 +48,11 @@ constexpr auto kStartReorderThreshold = 30;
|
|||
|
||||
} // namespace
|
||||
|
||||
class DialogsInner::SearchFromBubble : public Ui::MultiSelect::Item {
|
||||
public:
|
||||
using Item::Item;
|
||||
};
|
||||
|
||||
struct DialogsInner::ImportantSwitch {
|
||||
Dialogs::RippleRow row;
|
||||
};
|
||||
|
@ -123,8 +131,18 @@ int DialogsInner::peerSearchOffset() const {
|
|||
}
|
||||
|
||||
int DialogsInner::searchedOffset() const {
|
||||
int result = peerSearchOffset() + (_peerSearchResults.empty() ? 0 : ((_peerSearchResults.size() * st::dialogsRowHeight) + st::searchedBarHeight));
|
||||
if (_searchInPeer) result += st::dialogsRowHeight;
|
||||
auto result = peerSearchOffset() + (_peerSearchResults.empty() ? 0 : ((_peerSearchResults.size() * st::dialogsRowHeight) + st::searchedBarHeight));
|
||||
if (_searchInPeer) {
|
||||
result += searchInPeerSkip();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int DialogsInner::searchInPeerSkip() const {
|
||||
auto result = st::dialogsRowHeight;
|
||||
if (_searchFromUserBubble) {
|
||||
result += st::lineWidth + st::dialogsSearchFromPadding.top() + _searchFromUserBubble->rect().height() + st::dialogsSearchFromPadding.bottom();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -300,8 +318,8 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO
|
|||
}
|
||||
|
||||
if (_searchInPeer) {
|
||||
paintSearchInPeer(p, fullWidth, paintingOther);
|
||||
p.translate(0, st::dialogsRowHeight);
|
||||
paintSearchInPeer(p, fullWidth, paintingOther, ms);
|
||||
p.translate(0, searchInPeerSkip());
|
||||
if (_state == FilteredState && _searchResults.empty()) {
|
||||
p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg);
|
||||
if (!paintingOther) {
|
||||
|
@ -409,9 +427,13 @@ void DialogsInner::paintPeerSearchResult(Painter &p, const PeerSearchResult *res
|
|||
peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width());
|
||||
}
|
||||
|
||||
void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground) const {
|
||||
QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight);
|
||||
void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground, TimeMs ms) const {
|
||||
auto height = searchInPeerSkip();
|
||||
auto fullRect = QRect(0, 0, fullWidth, height);
|
||||
p.fillRect(fullRect, st::dialogsBg);
|
||||
if (_searchFromUserBubble) {
|
||||
p.fillRect(QRect(0, st::dialogsRowHeight, width(), st::lineWidth), st::shadowFg);
|
||||
}
|
||||
if (onlyBackground) return;
|
||||
|
||||
_searchInPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), getFullWidth(), st::dialogsPhotoSize);
|
||||
|
@ -432,14 +454,19 @@ void DialogsInner::paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackgro
|
|||
|
||||
p.setPen(st::dialogsNameFg);
|
||||
_searchInPeer->nameText.drawElided(p, rectForName.left(), rectForName.top(), rectForName.width());
|
||||
|
||||
if (_searchFromUserBubble) {
|
||||
_searchFromUserBubble->paint(p, width(), ms);
|
||||
}
|
||||
}
|
||||
|
||||
void DialogsInner::activate() {
|
||||
}
|
||||
|
||||
void DialogsInner::mouseMoveEvent(QMouseEvent *e) {
|
||||
auto position = e->pos();
|
||||
_mouseSelection = true;
|
||||
updateSelected(e->pos());
|
||||
updateSelected(position);
|
||||
}
|
||||
|
||||
void DialogsInner::clearIrrelevantState() {
|
||||
|
@ -465,6 +492,15 @@ void DialogsInner::updateSelected(QPoint localPos) {
|
|||
if (updateReorderPinned(localPos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_searchFromUserBubble) {
|
||||
if (_searchFromUserBubble->rect().contains(localPos)) {
|
||||
_searchFromUserBubble->mouseMoveEvent(localPos - _searchFromUserBubble->rect().topLeft());
|
||||
} else {
|
||||
_searchFromUserBubble->leaveEvent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_mouseSelection) {
|
||||
return;
|
||||
}
|
||||
|
@ -542,10 +578,26 @@ void DialogsInner::updateSelected(QPoint localPos) {
|
|||
}
|
||||
}
|
||||
|
||||
void DialogsInner::handleSearchFromUserClick() {
|
||||
Expects(_searchFromUserBubble != nullptr);
|
||||
if (_searchFromUserBubble->isOverDelete()) {
|
||||
searchFromUserChanged.notify(nullptr);
|
||||
} else {
|
||||
Dialogs::ShowSearchFromBox(_searchInPeer, base::lambda_guarded(this, [this](gsl::not_null<UserData*> user) {
|
||||
Ui::hideLayer();
|
||||
searchFromUserChanged.notify(user);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void DialogsInner::mousePressEvent(QMouseEvent *e) {
|
||||
_mouseSelection = true;
|
||||
updateSelected(e->pos());
|
||||
|
||||
if (_searchFromUserBubble && _searchFromUserBubble->rect().contains(e->pos())) {
|
||||
return handleSearchFromUserClick();
|
||||
}
|
||||
|
||||
_pressButton = e->button();
|
||||
setPressed(_selected);
|
||||
setImportantSwitchPressed(_importantSwitchSelected);
|
||||
|
@ -904,6 +956,13 @@ void DialogsInner::resizeEvent(QResizeEvent *e) {
|
|||
_addContactLnk->move((width() - _addContactLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2);
|
||||
auto widthForCancelButton = qMax(width() + otherWidth(), st::dialogsWidthMin);
|
||||
_cancelSearchInPeer->moveToLeft(widthForCancelButton - st::dialogsFilterSkip - st::dialogsFilterPadding.x() - _cancelSearchInPeer->width(), (st::dialogsRowHeight - st::dialogsCancelSearchInPeer.height) / 2);
|
||||
updateSearchFromBubble();
|
||||
}
|
||||
|
||||
void DialogsInner::updateSearchFromBubble() {
|
||||
if (_searchFromUserBubble) {
|
||||
_searchFromUserBubble->setPosition(st::dialogsSearchFromPadding.left(), st::dialogsRowHeight + st::lineWidth + st::dialogsSearchFromPadding.top(), width(), st::dialogsSearchFromPadding.left());
|
||||
}
|
||||
}
|
||||
|
||||
void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) {
|
||||
|
@ -1134,6 +1193,9 @@ void DialogsInner::updateSelectedRow(PeerData *peer) {
|
|||
void DialogsInner::leaveEventHook(QEvent *e) {
|
||||
setMouseTracking(false);
|
||||
clearSelection();
|
||||
if (_searchFromUserBubble) {
|
||||
_searchFromUserBubble->leaveEvent();
|
||||
}
|
||||
}
|
||||
|
||||
void DialogsInner::dragLeft() {
|
||||
|
@ -1233,15 +1295,8 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
|
|||
newFilter = words.isEmpty() ? QString() : words.join(' ');
|
||||
if (newFilter != _filter || force) {
|
||||
_filter = newFilter;
|
||||
if (!_searchInPeer && _filter.isEmpty()) {
|
||||
_state = DefaultState;
|
||||
_hashtagResults.clear();
|
||||
_filterResults.clear();
|
||||
_peerSearchResults.clear();
|
||||
_searchResults.clear();
|
||||
_lastSearchDate = 0;
|
||||
_lastSearchPeer = 0;
|
||||
_lastSearchId = _lastSearchMigratedId = 0;
|
||||
if (_filter.isEmpty() && !_searchFromUser) {
|
||||
clearFilter();
|
||||
} else {
|
||||
QStringList::const_iterator fb = words.cbegin(), fe = words.cend(), fi;
|
||||
|
||||
|
@ -1316,8 +1371,8 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
|
|||
}
|
||||
}
|
||||
}
|
||||
refresh(true);
|
||||
}
|
||||
refresh(true);
|
||||
setMouseSelection(false, true);
|
||||
}
|
||||
if (_state != DefaultState) {
|
||||
|
@ -1695,9 +1750,17 @@ bool DialogsInner::hasFilteredResults() const {
|
|||
return !_filterResults.isEmpty() && _hashtagResults.empty();
|
||||
}
|
||||
|
||||
void DialogsInner::searchInPeer(PeerData *peer) {
|
||||
void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
|
||||
_searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr;
|
||||
_searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : nullptr;
|
||||
_searchFromUser = from;
|
||||
if (_searchFromUser) {
|
||||
_searchFromUserBubble = std::make_unique<SearchFromBubble>(st::dialogsSearchFromBubble, _searchFromUser->id, App::peerName(_searchFromUser), st::activeButtonBg, PaintUserpicCallback(_searchFromUser));
|
||||
_searchFromUserBubble->setUpdateCallback([this] { update(0, st::dialogsRowHeight + st::lineWidth, width(), searchInPeerSkip() - st::dialogsRowHeight - st::lineWidth); });
|
||||
updateSearchFromBubble();
|
||||
} else {
|
||||
_searchFromUserBubble.reset();
|
||||
}
|
||||
if (_searchInPeer) {
|
||||
onHashtagFilterUpdate(QStringRef());
|
||||
_cancelSearchInPeer->show();
|
||||
|
@ -1708,7 +1771,7 @@ void DialogsInner::searchInPeer(PeerData *peer) {
|
|||
}
|
||||
|
||||
void DialogsInner::clearFilter() {
|
||||
if (_state == FilteredState || _state == SearchedState) {
|
||||
if (_state == FilteredState || _state == SearchedState || _searchInPeer) {
|
||||
if (_searchInPeer) {
|
||||
_state = FilteredState;
|
||||
} else {
|
||||
|
|
|
@ -96,7 +96,7 @@ public:
|
|||
State state() const;
|
||||
bool hasFilteredResults() const;
|
||||
|
||||
void searchInPeer(PeerData *peer);
|
||||
void searchInPeer(PeerData *peer, UserData *from);
|
||||
|
||||
void onFilterUpdate(QString newFilter, bool force = false);
|
||||
void onHashtagFilterUpdate(QStringRef newFilter);
|
||||
|
@ -108,6 +108,8 @@ public:
|
|||
}
|
||||
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
|
||||
|
||||
base::Observable<UserData*> searchFromUserChanged;
|
||||
|
||||
void notify_userIsContactChanged(UserData *user, bool fromThisApp);
|
||||
void notify_historyMuteUpdated(History *history);
|
||||
|
||||
|
@ -187,14 +189,17 @@ private:
|
|||
int filteredOffset() const;
|
||||
int peerSearchOffset() const;
|
||||
int searchedOffset() const;
|
||||
int searchInPeerSkip() const;
|
||||
|
||||
void paintDialog(Painter &p, Dialogs::Row *row, int fullWidth, PeerData *active, PeerData *selected, bool onlyBackground, TimeMs ms);
|
||||
void paintPeerSearchResult(Painter &p, const PeerSearchResult *result, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) const;
|
||||
void paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground) const;
|
||||
void paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground, TimeMs ms) const;
|
||||
|
||||
void clearSelection();
|
||||
void clearSearchResults(bool clearPeerSearchResults = true);
|
||||
void updateSelectedRow(PeerData *peer = 0);
|
||||
void updateSearchFromBubble();
|
||||
void handleSearchFromUserClick();
|
||||
|
||||
Dialogs::IndexedList *shownDialogs() const {
|
||||
return (Global::DialogsMode() == Dialogs::Mode::Important) ? _dialogsImportant.get() : _dialogs.get();
|
||||
|
@ -279,6 +284,9 @@ private:
|
|||
|
||||
PeerData *_searchInPeer = nullptr;
|
||||
PeerData *_searchInMigrated = nullptr;
|
||||
UserData *_searchFromUser = nullptr;
|
||||
class SearchFromBubble; // Just a wrap for Ui::MultiSelect::Item.
|
||||
std::unique_ptr<SearchFromBubble> _searchFromUserBubble;
|
||||
PeerData *_menuPeer = nullptr;
|
||||
|
||||
Ui::PopupMenu *_menu = nullptr;
|
||||
|
|
134
Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp
Normal file
134
Telegram/SourceFiles/dialogs/dialogs_search_from_controllers.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "observer_peer.h"
|
||||
#include "auth_session.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Dialogs {
|
||||
|
||||
void ShowSearchFromBox(PeerData *peer, base::lambda<void(gsl::not_null<UserData*>)> callback) {
|
||||
auto createController = [peer, callback = std::move(callback)]()->std::unique_ptr<PeerListController> {
|
||||
if (peer) {
|
||||
if (auto chat = peer->asChat()) {
|
||||
return std::make_unique<Dialogs::ChatSearchFromController>(chat, std::move(callback));
|
||||
} else if (auto group = peer->asMegagroup()) {
|
||||
return std::make_unique<Dialogs::ChannelSearchFromController>(group, std::move(callback));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
if (auto controller = createController()) {
|
||||
Ui::show(Box<PeerListBox>(std::move(controller), [](PeerListBox *box) {
|
||||
box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); });
|
||||
}), KeepOtherLayers);
|
||||
}
|
||||
}
|
||||
|
||||
ChatSearchFromController::ChatSearchFromController(gsl::not_null<ChatData*> chat, base::lambda<void(gsl::not_null<UserData*>)> callback) : PeerListController()
|
||||
, _chat(chat)
|
||||
, _callback(std::move(callback)) {
|
||||
}
|
||||
|
||||
void ChatSearchFromController::prepare() {
|
||||
setSearchNoResultsText(lang(lng_blocked_list_not_found));
|
||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
||||
delegate()->peerListSetTitle(langFactory(lng_search_messages_from));
|
||||
|
||||
rebuildRows();
|
||||
|
||||
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::MembersChanged, [this](const Notify::PeerUpdate &update) {
|
||||
if (update.peer == _chat) {
|
||||
rebuildRows();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void ChatSearchFromController::rowClicked(gsl::not_null<PeerListRow*> row) {
|
||||
Expects(row->peer()->isUser());
|
||||
_callback(row->peer()->asUser());
|
||||
}
|
||||
|
||||
void ChatSearchFromController::rebuildRows() {
|
||||
auto ms = getms();
|
||||
auto wasEmpty = !delegate()->peerListFullRowsCount();
|
||||
|
||||
auto now = unixtime();
|
||||
QMultiMap<int32, UserData*> ordered;
|
||||
if (_chat->noParticipantInfo()) {
|
||||
AuthSession::Current().api().requestFullPeer(_chat);
|
||||
} else if (!_chat->participants.isEmpty()) {
|
||||
for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
|
||||
auto user = i.key();
|
||||
ordered.insertMulti(App::onlineForSort(user, now), user);
|
||||
}
|
||||
}
|
||||
for_const (auto user, _chat->lastAuthors) {
|
||||
if (user->isInaccessible()) continue;
|
||||
appendRow(user);
|
||||
if (!ordered.isEmpty()) {
|
||||
ordered.remove(App::onlineForSort(user, now), user);
|
||||
}
|
||||
}
|
||||
if (!ordered.isEmpty()) {
|
||||
for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) {
|
||||
appendRow(*(--i));
|
||||
}
|
||||
}
|
||||
checkForEmptyRows();
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void ChatSearchFromController::checkForEmptyRows() {
|
||||
if (delegate()->peerListFullRowsCount()) {
|
||||
setDescriptionText(QString());
|
||||
} else {
|
||||
setDescriptionText(lang(lng_contacts_loading));
|
||||
}
|
||||
}
|
||||
|
||||
void ChatSearchFromController::appendRow(gsl::not_null<UserData*> user) {
|
||||
if (!delegate()->peerListFindRow(user->id)) {
|
||||
delegate()->peerListAppendRow(std::make_unique<PeerListRow>(user));
|
||||
}
|
||||
}
|
||||
|
||||
ChannelSearchFromController::ChannelSearchFromController(gsl::not_null<ChannelData*> channel, base::lambda<void(gsl::not_null<UserData*>)> callback) : ParticipantsBoxController(channel, ParticipantsBoxController::Role::Members)
|
||||
, _callback(std::move(callback)) {
|
||||
}
|
||||
|
||||
void ChannelSearchFromController::prepare() {
|
||||
ParticipantsBoxController::prepare();
|
||||
delegate()->peerListSetTitle(langFactory(lng_search_messages_from));
|
||||
}
|
||||
|
||||
void ChannelSearchFromController::rowClicked(gsl::not_null<PeerListRow*> row) {
|
||||
Expects(row->peer()->isUser());
|
||||
_callback(row->peer()->asUser());
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ChannelSearchFromController::createRow(gsl::not_null<UserData*> user) const {
|
||||
return std::make_unique<PeerListRow>(user);
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "profile/profile_channel_controllers.h"
|
||||
|
||||
namespace Dialogs {
|
||||
|
||||
void ShowSearchFromBox(PeerData *peer, base::lambda<void(gsl::not_null<UserData*>)> callback);
|
||||
|
||||
class ChatSearchFromController : public PeerListController, protected base::Subscriber {
|
||||
public:
|
||||
ChatSearchFromController(gsl::not_null<ChatData*> chat, base::lambda<void(gsl::not_null<UserData*>)> callback);
|
||||
|
||||
void prepare() override;
|
||||
void rowClicked(gsl::not_null<PeerListRow*> row) override;
|
||||
|
||||
private:
|
||||
void rebuildRows();
|
||||
void checkForEmptyRows();
|
||||
void appendRow(gsl::not_null<UserData*> user);
|
||||
|
||||
gsl::not_null<ChatData*> _chat;
|
||||
base::lambda<void(gsl::not_null<UserData*>)> _callback;
|
||||
|
||||
};
|
||||
|
||||
class ChannelSearchFromController : public Profile::ParticipantsBoxController {
|
||||
public:
|
||||
ChannelSearchFromController(gsl::not_null<ChannelData*> channel, base::lambda<void(gsl::not_null<UserData*>)> callback);
|
||||
|
||||
void prepare() override;
|
||||
void rowClicked(gsl::not_null<PeerListRow*> row) override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(gsl::not_null<UserData*> user) const override;
|
||||
|
||||
private:
|
||||
base::lambda<void(gsl::not_null<UserData*>)> _callback;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Dialogs
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "dialogs/dialogs_widget.h"
|
||||
|
||||
#include "dialogs/dialogs_inner_widget.h"
|
||||
#include "dialogs/dialogs_search_from_controllers.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
@ -32,7 +33,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "auth_session.h"
|
||||
#include "messenger.h"
|
||||
#include "ui/effects/widget_fade_wrap.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "profile/profile_channel_controllers.h"
|
||||
|
||||
class DialogsWidget::UpdateButton : public Ui::RippleButton {
|
||||
public:
|
||||
|
@ -83,7 +86,7 @@ void DialogsWidget::UpdateButton::paintEvent(QPaintEvent *e) {
|
|||
DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*> controller) : Window::AbstractSectionWidget(parent, controller)
|
||||
, _mainMenuToggle(this, st::dialogsMenuToggle)
|
||||
, _filter(this, st::dialogsFilter, langFactory(lng_dlg_filter))
|
||||
, _searchFromUser(this, object_ptr<Ui::IconButton>(this, st::dialogsSearchFrom))
|
||||
, _chooseFromUser(this, object_ptr<Ui::IconButton>(this, st::dialogsSearchFrom))
|
||||
, _jumpToDate(this, object_ptr<Ui::IconButton>(this, st::dialogsCalendar))
|
||||
, _cancelSearch(this, st::dialogsCancelSearch)
|
||||
, _lockUnlock(this, st::dialogsLock)
|
||||
|
@ -97,6 +100,10 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*>
|
|||
connect(_inner, SIGNAL(completeHashtag(QString)), this, SLOT(onCompleteHashtag(QString)));
|
||||
connect(_inner, SIGNAL(refreshHashtags()), this, SLOT(onFilterCursorMoved()));
|
||||
connect(_inner, SIGNAL(cancelSearchInPeer()), this, SLOT(onCancelSearchInPeer()));
|
||||
subscribe(_inner->searchFromUserChanged, [this](UserData *user) {
|
||||
setSearchInPeer(_searchInPeer, user);
|
||||
onFilterUpdate(true);
|
||||
});
|
||||
connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged()));
|
||||
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll()));
|
||||
connect(_filter, SIGNAL(cancelled()), this, SLOT(onCancel()));
|
||||
|
@ -114,7 +121,7 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*>
|
|||
|
||||
_cancelSearch->setClickedCallback([this] { onCancelSearch(); });
|
||||
_jumpToDate->entity()->setClickedCallback([this] { if (_searchInPeer) this->controller()->showJumpToDate(_searchInPeer, QDate()); });
|
||||
_searchFromUser->entity()->setClickedCallback([this] { if (_searchInPeer->isChat() || _searchInPeer->isMegagroup()) showSearchFrom(); });
|
||||
_chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); });
|
||||
_lockUnlock->setVisible(Global::LocalPasscode());
|
||||
subscribe(Global::RefLocalPasscodeChanged(), [this] { updateLockUnlockVisibility(); });
|
||||
_lockUnlock->setClickedCallback([this] {
|
||||
|
@ -144,6 +151,7 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*>
|
|||
_filter->customUpDown(true);
|
||||
|
||||
updateJumpToDateVisibility(true);
|
||||
updateSearchFromVisibility(true);
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
|
@ -241,7 +249,7 @@ void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window:
|
|||
_filter->hide();
|
||||
_cancelSearch->hideFast();
|
||||
_jumpToDate->hideFast();
|
||||
_searchFromUser->hideFast();
|
||||
_chooseFromUser->hideFast();
|
||||
_lockUnlock->hide();
|
||||
|
||||
int delta = st::slideShift;
|
||||
|
@ -270,6 +278,7 @@ void DialogsWidget::animationCallback() {
|
|||
_filter->show();
|
||||
updateLockUnlockVisibility();
|
||||
updateJumpToDateVisibility(true);
|
||||
updateSearchFromVisibility(true);
|
||||
|
||||
onFilterUpdate();
|
||||
if (App::wnd()) App::wnd()->setInnerFocus();
|
||||
|
@ -450,8 +459,8 @@ void DialogsWidget::onDraggingScrollTimer() {
|
|||
}
|
||||
|
||||
bool DialogsWidget::onSearchMessages(bool searchCache) {
|
||||
QString q = _filter->getLastText().trimmed();
|
||||
if (q.isEmpty()) {
|
||||
auto q = _filter->getLastText().trimmed();
|
||||
if (q.isEmpty() && !_searchFromUser) {
|
||||
MTP::cancel(base::take(_searchRequest));
|
||||
MTP::cancel(base::take(_peerSearchRequest));
|
||||
return true;
|
||||
|
@ -460,17 +469,20 @@ bool DialogsWidget::onSearchMessages(bool searchCache) {
|
|||
SearchCache::const_iterator i = _searchCache.constFind(q);
|
||||
if (i != _searchCache.cend()) {
|
||||
_searchQuery = q;
|
||||
_searchQueryFrom = _searchFromUser;
|
||||
_searchFull = _searchFullMigrated = false;
|
||||
MTP::cancel(base::take(_searchRequest));
|
||||
searchReceived(_searchInPeer ? DialogsSearchPeerFromStart : DialogsSearchFromStart, i.value(), 0);
|
||||
return true;
|
||||
}
|
||||
} else if (_searchQuery != q) {
|
||||
} else if (_searchQuery != q || _searchQueryFrom != _searchFromUser) {
|
||||
_searchQuery = q;
|
||||
_searchQueryFrom = _searchFromUser;
|
||||
_searchFull = _searchFullMigrated = false;
|
||||
MTP::cancel(base::take(_searchRequest));
|
||||
if (_searchInPeer) {
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart));
|
||||
auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0);
|
||||
_searchRequest = MTP::send(MTPmessages_Search(flags, _searchInPeer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart));
|
||||
} else {
|
||||
_searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(0), MTP_inputPeerEmpty(), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart));
|
||||
}
|
||||
|
@ -532,7 +544,8 @@ void DialogsWidget::onSearchMore() {
|
|||
auto offsetPeer = _inner->lastSearchPeer();
|
||||
auto offsetId = _inner->lastSearchId();
|
||||
if (_searchInPeer) {
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart));
|
||||
auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0);
|
||||
_searchRequest = MTP::send(MTPmessages_Search(flags, _searchInPeer->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart));
|
||||
} else {
|
||||
_searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(offsetDate), offsetPeer ? offsetPeer->input : MTP_inputPeerEmpty(), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart));
|
||||
}
|
||||
|
@ -541,7 +554,8 @@ void DialogsWidget::onSearchMore() {
|
|||
}
|
||||
} else if (_searchInMigrated && !_searchFullMigrated) {
|
||||
auto offsetMigratedId = _inner->lastSearchMigratedId();
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(0), _searchInMigrated->input, MTP_string(_searchQuery), MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart));
|
||||
auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0);
|
||||
_searchRequest = MTP::send(MTPmessages_Search(flags, _searchInMigrated->input, MTP_string(_searchQuery), _searchQueryFrom ? _searchQueryFrom->inputUser : MTP_inputUserEmpty(), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -783,9 +797,7 @@ void DialogsWidget::onFilterUpdate(bool force) {
|
|||
auto filterText = _filter->getLastText();
|
||||
_inner->onFilterUpdate(filterText, force);
|
||||
if (filterText.isEmpty()) {
|
||||
_searchCache.clear();
|
||||
_searchQueries.clear();
|
||||
_searchQuery = QString();
|
||||
clearSearchCache();
|
||||
_cancelSearch->hideAnimated();
|
||||
} else {
|
||||
_cancelSearch->showAnimated();
|
||||
|
@ -805,19 +817,42 @@ void DialogsWidget::searchInPeer(PeerData *peer) {
|
|||
onFilterUpdate(true);
|
||||
}
|
||||
|
||||
void DialogsWidget::setSearchInPeer(PeerData *peer) {
|
||||
void DialogsWidget::setSearchInPeer(PeerData *peer, UserData *from) {
|
||||
auto searchInPeerUpdated = false;
|
||||
auto newSearchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr;
|
||||
_searchInMigrated = newSearchInPeer ? newSearchInPeer->migrateFrom() : nullptr;
|
||||
if (newSearchInPeer != _searchInPeer) {
|
||||
searchInPeerUpdated = (newSearchInPeer != _searchInPeer);
|
||||
if (searchInPeerUpdated) {
|
||||
_searchInPeer = newSearchInPeer;
|
||||
from = nullptr;
|
||||
controller()->searchInPeerChanged().notify(_searchInPeer, true);
|
||||
updateJumpToDateVisibility();
|
||||
} else if (!_searchInPeer) {
|
||||
from = nullptr;
|
||||
}
|
||||
_inner->searchInPeer(_searchInPeer);
|
||||
if (_searchFromUser != from || searchInPeerUpdated) {
|
||||
_searchFromUser = from;
|
||||
updateSearchFromVisibility();
|
||||
clearSearchCache();
|
||||
}
|
||||
_inner->searchInPeer(_searchInPeer, _searchFromUser);
|
||||
}
|
||||
|
||||
void DialogsWidget::clearSearchCache() {
|
||||
_searchCache.clear();
|
||||
_searchQueries.clear();
|
||||
_searchQuery = QString();
|
||||
_searchQueryFrom = nullptr;
|
||||
MTP::cancel(base::take(_searchRequest));
|
||||
}
|
||||
|
||||
void DialogsWidget::showSearchFrom() {
|
||||
|
||||
auto peer = _searchInPeer;
|
||||
Dialogs::ShowSearchFromBox(peer, base::lambda_guarded(this, [this, peer](gsl::not_null<UserData*> user) {
|
||||
Ui::hideLayer();
|
||||
setSearchInPeer(peer, user);
|
||||
onFilterUpdate(true);
|
||||
}));
|
||||
}
|
||||
|
||||
void DialogsWidget::onFilterCursorMoved(int from, int to) {
|
||||
|
@ -883,12 +918,14 @@ void DialogsWidget::updateJumpToDateVisibility(bool fast) {
|
|||
} else {
|
||||
_jumpToDate->toggleAnimated(jumpToDateVisible);
|
||||
}
|
||||
}
|
||||
|
||||
auto searchFromUserVisible = _searchInPeer && (_searchInPeer->isChat() || _searchInPeer->isMegagroup());
|
||||
void DialogsWidget::updateSearchFromVisibility(bool fast) {
|
||||
auto searchFromUserVisible = _searchInPeer && (_searchInPeer->isChat() || _searchInPeer->isMegagroup()) && !_searchFromUser;
|
||||
if (fast) {
|
||||
_searchFromUser->toggleFast(searchFromUserVisible);
|
||||
_chooseFromUser->toggleFast(searchFromUserVisible);
|
||||
} else {
|
||||
_searchFromUser->toggleAnimated(searchFromUserVisible);
|
||||
_chooseFromUser->toggleAnimated(searchFromUserVisible);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -909,10 +946,11 @@ void DialogsWidget::updateControlsGeometry() {
|
|||
_filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height());
|
||||
auto mainMenuLeft = anim::interpolate(st::dialogsFilterPadding.x(), (smallLayoutWidth - _mainMenuToggle->width()) / 2, smallLayoutRatio);
|
||||
_mainMenuToggle->moveToLeft(mainMenuLeft, filterAreaTop + st::dialogsFilterPadding.y());
|
||||
_lockUnlock->moveToLeft(filterLeft + filterWidth + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y());
|
||||
_cancelSearch->moveToLeft(filterLeft + filterWidth - _cancelSearch->width(), _filter->y());
|
||||
_jumpToDate->moveToLeft(filterLeft + filterWidth - _jumpToDate->width(), _filter->y());
|
||||
_searchFromUser->moveToLeft(filterLeft + filterWidth - _jumpToDate->width() - _searchFromUser->width(), _filter->y());
|
||||
auto right = filterLeft + filterWidth;
|
||||
_lockUnlock->moveToLeft(right + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y());
|
||||
_cancelSearch->moveToLeft(right - _cancelSearch->width(), _filter->y());
|
||||
right -= _jumpToDate->width(); _jumpToDate->moveToLeft(right, _filter->y());
|
||||
right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _filter->y());
|
||||
|
||||
auto scrollTop = filterAreaTop + filterAreaHeight;
|
||||
auto addToScroll = App::main() ? App::main()->contentScrollAddToY() : 0;
|
||||
|
|
|
@ -150,11 +150,13 @@ private:
|
|||
void searchReceived(DialogsSearchRequestType type, const MTPmessages_Messages &result, mtpRequestId requestId);
|
||||
void peerSearchReceived(const MTPcontacts_Found &result, mtpRequestId requestId);
|
||||
|
||||
void setSearchInPeer(PeerData *peer);
|
||||
void setSearchInPeer(PeerData *peer, UserData *from = nullptr);
|
||||
void showSearchFrom();
|
||||
void showMainMenu();
|
||||
void clearSearchCache();
|
||||
void updateLockUnlockVisibility();
|
||||
void updateJumpToDateVisibility(bool fast = false);
|
||||
void updateSearchFromVisibility(bool fast = false);
|
||||
void updateControlsGeometry();
|
||||
void updateForwardBar();
|
||||
|
||||
|
@ -180,7 +182,7 @@ private:
|
|||
object_ptr<Ui::IconButton> _forwardCancel = { nullptr };
|
||||
object_ptr<Ui::IconButton> _mainMenuToggle;
|
||||
object_ptr<Ui::FlatInput> _filter;
|
||||
object_ptr<Ui::WidgetScaledFadeWrap<Ui::IconButton>> _searchFromUser;
|
||||
object_ptr<Ui::WidgetScaledFadeWrap<Ui::IconButton>> _chooseFromUser;
|
||||
object_ptr<Ui::WidgetScaledFadeWrap<Ui::IconButton>> _jumpToDate;
|
||||
object_ptr<Ui::CrossButton> _cancelSearch;
|
||||
object_ptr<Ui::IconButton> _lockUnlock;
|
||||
|
@ -195,6 +197,7 @@ private:
|
|||
|
||||
PeerData *_searchInPeer = nullptr;
|
||||
PeerData *_searchInMigrated = nullptr;
|
||||
UserData *_searchFromUser = nullptr;
|
||||
|
||||
QTimer _searchTimer;
|
||||
|
||||
|
@ -203,6 +206,7 @@ private:
|
|||
mtpRequestId _peerSearchRequest = 0;
|
||||
|
||||
QString _searchQuery;
|
||||
UserData *_searchQueryFrom = nullptr;
|
||||
bool _searchFull = false;
|
||||
bool _searchFullMigrated = false;
|
||||
mtpRequestId _searchRequest = 0;
|
||||
|
|
|
@ -65,6 +65,9 @@ public:
|
|||
template <typename Callback>
|
||||
static void HandleParticipant(const MTPChannelParticipant &participant, Role role, gsl::not_null<Additional*> additional, Callback callback);
|
||||
|
||||
protected:
|
||||
virtual std::unique_ptr<PeerListRow> createRow(gsl::not_null<UserData*> user) const;
|
||||
|
||||
private:
|
||||
static std::unique_ptr<PeerListSearchController> CreateSearchController(gsl::not_null<ChannelData*> channel, Role role, gsl::not_null<Additional*> additional);
|
||||
|
||||
|
@ -78,7 +81,6 @@ private:
|
|||
bool appendRow(gsl::not_null<UserData*> user);
|
||||
bool prependRow(gsl::not_null<UserData*> user);
|
||||
bool removeRow(gsl::not_null<UserData*> user);
|
||||
std::unique_ptr<PeerListRow> createRow(gsl::not_null<UserData*> user) const;
|
||||
void refreshCustomStatus(gsl::not_null<PeerListRow*> row) const;
|
||||
bool feedMegagroupLastParticipants();
|
||||
|
||||
|
|
|
@ -34,89 +34,7 @@ constexpr int kWideScale = 3;
|
|||
|
||||
} // namespace
|
||||
|
||||
class MultiSelect::Inner::Item {
|
||||
public:
|
||||
Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage);
|
||||
|
||||
uint64 id() const {
|
||||
return _id;
|
||||
}
|
||||
int getWidth() const {
|
||||
return _width;
|
||||
}
|
||||
QRect rect() const {
|
||||
return QRect(_x, _y, _width, _st.height);
|
||||
}
|
||||
bool isOverDelete() const {
|
||||
return _overDelete;
|
||||
}
|
||||
void setActive(bool active) {
|
||||
_active = active;
|
||||
}
|
||||
void setPosition(int x, int y, int outerWidth, int maxVisiblePadding);
|
||||
QRect paintArea(int outerWidth) const;
|
||||
|
||||
void setUpdateCallback(base::lambda<void()> updateCallback) {
|
||||
_updateCallback = updateCallback;
|
||||
}
|
||||
void setText(const QString &text);
|
||||
void paint(Painter &p, int outerWidth, TimeMs ms);
|
||||
|
||||
void mouseMoveEvent(QPoint point);
|
||||
void leaveEvent();
|
||||
|
||||
void showAnimated() {
|
||||
setVisibleAnimated(true);
|
||||
}
|
||||
void hideAnimated() {
|
||||
setVisibleAnimated(false);
|
||||
}
|
||||
bool hideFinished() const {
|
||||
return (_hiding && !_visibility.animating());
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void setOver(bool over);
|
||||
void paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms);
|
||||
void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity);
|
||||
bool paintCached(Painter &p, int x, int y, int outerWidth);
|
||||
void prepareCache();
|
||||
void setVisibleAnimated(bool visible);
|
||||
|
||||
const style::MultiSelectItem &_st;
|
||||
|
||||
uint64 _id;
|
||||
struct SlideAnimation {
|
||||
SlideAnimation(base::lambda<void()> updateCallback, int fromX, int toX, int y, float64 duration)
|
||||
: fromX(fromX)
|
||||
, toX(toX)
|
||||
, y(y) {
|
||||
x.start(updateCallback, fromX, toX, duration);
|
||||
}
|
||||
Animation x;
|
||||
int fromX, toX;
|
||||
int y;
|
||||
};
|
||||
std::vector<SlideAnimation> _copies;
|
||||
int _x = -1;
|
||||
int _y = -1;
|
||||
int _width = 0;
|
||||
Text _text;
|
||||
style::color _color;
|
||||
bool _over = false;
|
||||
QPixmap _cache;
|
||||
Animation _visibility;
|
||||
Animation _overOpacity;
|
||||
bool _overDelete = false;
|
||||
bool _active = false;
|
||||
PaintRoundImage _paintRoundImage;
|
||||
base::lambda<void()> _updateCallback;
|
||||
bool _hiding = false;
|
||||
|
||||
};
|
||||
|
||||
MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage)
|
||||
MultiSelect::Item::Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage)
|
||||
: _st(st)
|
||||
, _id(id)
|
||||
, _color(color)
|
||||
|
@ -124,13 +42,13 @@ MultiSelect::Inner::Item::Item(const style::MultiSelectItem &st, uint64 id, cons
|
|||
setText(text);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::setText(const QString &text) {
|
||||
void MultiSelect::Item::setText(const QString &text) {
|
||||
_text.setText(_st.style, text, _textNameOptions);
|
||||
_width = _st.height + _st.padding.left() + _text.maxWidth() + _st.padding.right();
|
||||
accumulate_min(_width, _st.maxWidth);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, TimeMs ms) {
|
||||
void MultiSelect::Item::paint(Painter &p, int outerWidth, TimeMs ms) {
|
||||
if (!_cache.isNull() && !_visibility.animating(ms)) {
|
||||
if (_hiding) {
|
||||
return;
|
||||
|
@ -158,7 +76,7 @@ void MultiSelect::Inner::Item::paint(Painter &p, int outerWidth, TimeMs ms) {
|
|||
}
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms) {
|
||||
void MultiSelect::Item::paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms) {
|
||||
if (!_cache.isNull()) {
|
||||
paintCached(p, x, y, outerWidth);
|
||||
return;
|
||||
|
@ -198,7 +116,7 @@ void MultiSelect::Inner::Item::paintOnce(Painter &p, int x, int y, int outerWidt
|
|||
_text.drawLeftElided(p, x + textLeft, y + _st.padding.top(), textWidth, outerWidth);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) {
|
||||
void MultiSelect::Item::paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity) {
|
||||
p.setOpacity(overOpacity);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
|
@ -213,7 +131,7 @@ void MultiSelect::Inner::Item::paintDeleteButton(Painter &p, int x, int y, int o
|
|||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWidth) {
|
||||
bool MultiSelect::Item::paintCached(Painter &p, int x, int y, int outerWidth) {
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
auto opacity = _visibility.current(_hiding ? 0. : 1.);
|
||||
|
@ -227,18 +145,18 @@ bool MultiSelect::Inner::Item::paintCached(Painter &p, int x, int y, int outerWi
|
|||
return true;
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::mouseMoveEvent(QPoint point) {
|
||||
void MultiSelect::Item::mouseMoveEvent(QPoint point) {
|
||||
if (!_cache.isNull()) return;
|
||||
_overDelete = QRect(0, 0, _st.height, _st.height).contains(point);
|
||||
setOver(true);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::leaveEvent() {
|
||||
void MultiSelect::Item::leaveEvent() {
|
||||
_overDelete = false;
|
||||
setOver(false);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) {
|
||||
void MultiSelect::Item::setPosition(int x, int y, int outerWidth, int maxVisiblePadding) {
|
||||
if (_x >= 0 && _y >= 0 && (_x != x || _y != y)) {
|
||||
// Make an animation if it is not the first setPosition().
|
||||
auto found = false;
|
||||
|
@ -277,7 +195,7 @@ void MultiSelect::Inner::Item::setPosition(int x, int y, int outerWidth, int max
|
|||
_y = y;
|
||||
}
|
||||
|
||||
QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const {
|
||||
QRect MultiSelect::Item::paintArea(int outerWidth) const {
|
||||
if (_copies.empty()) {
|
||||
return rect();
|
||||
}
|
||||
|
@ -293,7 +211,7 @@ QRect MultiSelect::Inner::Item::paintArea(int outerWidth) const {
|
|||
return QRect(0, yMin, outerWidth, yMax - yMin + _st.height);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::prepareCache() {
|
||||
void MultiSelect::Item::prepareCache() {
|
||||
if (!_cache.isNull()) return;
|
||||
|
||||
t_assert(!_visibility.animating());
|
||||
|
@ -309,7 +227,7 @@ void MultiSelect::Inner::Item::prepareCache() {
|
|||
_cache = App::pixmapFromImageInPlace(std::move(data));
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) {
|
||||
void MultiSelect::Item::setVisibleAnimated(bool visible) {
|
||||
_hiding = !visible;
|
||||
prepareCache();
|
||||
auto from = visible ? 0. : 1.;
|
||||
|
@ -318,7 +236,7 @@ void MultiSelect::Inner::Item::setVisibleAnimated(bool visible) {
|
|||
_visibility.start(_updateCallback, from, to, _st.duration, transition);
|
||||
}
|
||||
|
||||
void MultiSelect::Inner::Item::setOver(bool over) {
|
||||
void MultiSelect::Item::setOver(bool over) {
|
||||
if (over != _over) {
|
||||
_over = over;
|
||||
_overOpacity.start(_updateCallback, _over ? 0. : 1., _over ? 1. : 0., _st.duration);
|
||||
|
@ -407,7 +325,7 @@ void MultiSelect::addItem(uint64 itemId, const QString &text, style::color color
|
|||
}
|
||||
|
||||
void MultiSelect::addItemInBunch(uint64 itemId, const QString &text, style::color color, PaintRoundImage paintRoundImage) {
|
||||
_inner->addItemInBunch(std::make_unique<Inner::Item>(_st.item, itemId, text, color, std::move(paintRoundImage)));
|
||||
_inner->addItemInBunch(std::make_unique<Item>(_st.item, itemId, text, color, std::move(paintRoundImage)));
|
||||
}
|
||||
|
||||
void MultiSelect::finishItemsBunch() {
|
||||
|
|
|
@ -57,6 +57,8 @@ public:
|
|||
QVector<uint64> getItems() const;
|
||||
bool hasItem(uint64 itemId) const;
|
||||
|
||||
class Item;
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
|
@ -91,7 +93,6 @@ public:
|
|||
void setQueryChangedCallback(base::lambda<void(const QString &query)> callback);
|
||||
void setSubmittedCallback(base::lambda<void(bool ctrlShiftEnter)> callback);
|
||||
|
||||
class Item;
|
||||
void addItemInBunch(std::unique_ptr<Item> item);
|
||||
void finishItemsBunch(AddItemWay way);
|
||||
void setItemText(uint64 itemId, const QString &text);
|
||||
|
@ -176,4 +177,87 @@ private:
|
|||
|
||||
};
|
||||
|
||||
|
||||
class MultiSelect::Item {
|
||||
public:
|
||||
Item(const style::MultiSelectItem &st, uint64 id, const QString &text, style::color color, PaintRoundImage &&paintRoundImage);
|
||||
|
||||
uint64 id() const {
|
||||
return _id;
|
||||
}
|
||||
int getWidth() const {
|
||||
return _width;
|
||||
}
|
||||
QRect rect() const {
|
||||
return QRect(_x, _y, _width, _st.height);
|
||||
}
|
||||
bool isOverDelete() const {
|
||||
return _overDelete;
|
||||
}
|
||||
void setActive(bool active) {
|
||||
_active = active;
|
||||
}
|
||||
void setPosition(int x, int y, int outerWidth, int maxVisiblePadding);
|
||||
QRect paintArea(int outerWidth) const;
|
||||
|
||||
void setUpdateCallback(base::lambda<void()> updateCallback) {
|
||||
_updateCallback = updateCallback;
|
||||
}
|
||||
void setText(const QString &text);
|
||||
void paint(Painter &p, int outerWidth, TimeMs ms);
|
||||
|
||||
void mouseMoveEvent(QPoint point);
|
||||
void leaveEvent();
|
||||
|
||||
void showAnimated() {
|
||||
setVisibleAnimated(true);
|
||||
}
|
||||
void hideAnimated() {
|
||||
setVisibleAnimated(false);
|
||||
}
|
||||
bool hideFinished() const {
|
||||
return (_hiding && !_visibility.animating());
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void setOver(bool over);
|
||||
void paintOnce(Painter &p, int x, int y, int outerWidth, TimeMs ms);
|
||||
void paintDeleteButton(Painter &p, int x, int y, int outerWidth, float64 overOpacity);
|
||||
bool paintCached(Painter &p, int x, int y, int outerWidth);
|
||||
void prepareCache();
|
||||
void setVisibleAnimated(bool visible);
|
||||
|
||||
const style::MultiSelectItem &_st;
|
||||
|
||||
uint64 _id;
|
||||
struct SlideAnimation {
|
||||
SlideAnimation(base::lambda<void()> updateCallback, int fromX, int toX, int y, float64 duration)
|
||||
: fromX(fromX)
|
||||
, toX(toX)
|
||||
, y(y) {
|
||||
x.start(updateCallback, fromX, toX, duration);
|
||||
}
|
||||
Animation x;
|
||||
int fromX, toX;
|
||||
int y;
|
||||
};
|
||||
std::vector<SlideAnimation> _copies;
|
||||
int _x = -1;
|
||||
int _y = -1;
|
||||
int _width = 0;
|
||||
Text _text;
|
||||
style::color _color;
|
||||
bool _over = false;
|
||||
QPixmap _cache;
|
||||
Animation _visibility;
|
||||
Animation _overOpacity;
|
||||
bool _overDelete = false;
|
||||
bool _active = false;
|
||||
PaintRoundImage _paintRoundImage;
|
||||
base::lambda<void()> _updateCallback;
|
||||
bool _hiding = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -741,6 +741,26 @@ defaultIconButton: IconButton {
|
|||
iconPosition: point(-1px, -1px);
|
||||
}
|
||||
|
||||
defaultMultiSelectItem: MultiSelectItem {
|
||||
padding: margins(6px, 7px, 12px, 0px);
|
||||
maxWidth: 128px;
|
||||
height: 32px;
|
||||
style: defaultTextStyle;
|
||||
textBg: contactsBgOver;
|
||||
textFg: windowFg;
|
||||
textActiveBg: activeButtonBg;
|
||||
textActiveFg: activeButtonFg;
|
||||
deleteFg: activeButtonFg;
|
||||
deleteCross: CrossAnimation {
|
||||
size: 32px;
|
||||
skip: 10px;
|
||||
stroke: 2px;
|
||||
minScale: 0.3;
|
||||
}
|
||||
duration: 150;
|
||||
minScale: 0.3;
|
||||
}
|
||||
|
||||
widgetSlideDuration: 200;
|
||||
widgetFadeDuration: 200;
|
||||
|
||||
|
|
|
@ -144,6 +144,8 @@
|
|||
<(src_loc)/dialogs/dialogs_list.h
|
||||
<(src_loc)/dialogs/dialogs_row.cpp
|
||||
<(src_loc)/dialogs/dialogs_row.h
|
||||
<(src_loc)/dialogs/dialogs_search_from_controllers.cpp
|
||||
<(src_loc)/dialogs/dialogs_search_from_controllers.h
|
||||
<(src_loc)/dialogs/dialogs_widget.cpp
|
||||
<(src_loc)/dialogs/dialogs_widget.h
|
||||
<(src_loc)/history/history.cpp
|
||||
|
|
Loading…
Add table
Reference in a new issue