mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Show correct downloads elements context menu.
This commit is contained in:
parent
daadf7e2a1
commit
e89c95551f
32 changed files with 362 additions and 147 deletions
|
@ -2601,7 +2601,9 @@ void GroupCall::requestCurrentTimeStart(
|
|||
});
|
||||
}).fail([=] {
|
||||
finish(0);
|
||||
}).handleAllErrors().send();
|
||||
}).handleAllErrors().toDC(
|
||||
MTP::groupCallStreamDcId(_broadcastDcId)
|
||||
).send();
|
||||
}
|
||||
|
||||
void GroupCall::requestCurrentTimeCancel(
|
||||
|
|
|
@ -242,6 +242,73 @@ void DownloadManager::addLoaded(
|
|||
}
|
||||
}
|
||||
|
||||
void DownloadManager::deleteFiles(const std::vector<GlobalMsgId> &ids) {
|
||||
struct DocumentDescriptor {
|
||||
uint64 sessionUniqueId = 0;
|
||||
DocumentId documentId = 0;
|
||||
FullMsgId itemId;
|
||||
};
|
||||
auto sessions = base::flat_set<not_null<Main::Session*>>();
|
||||
auto files = base::flat_map<QString, DocumentDescriptor>();
|
||||
for (const auto &id : ids) {
|
||||
if (const auto item = MessageByGlobalId(id)) {
|
||||
const auto session = &item->history()->session();
|
||||
const auto i = _sessions.find(session);
|
||||
if (i == end(_sessions)) {
|
||||
continue;
|
||||
}
|
||||
auto &data = i->second;
|
||||
const auto j = ranges::find(
|
||||
data.downloading,
|
||||
not_null{ item },
|
||||
ByItem);
|
||||
if (j != end(data.downloading)) {
|
||||
cancel(data, j);
|
||||
}
|
||||
|
||||
const auto k = ranges::find(data.downloaded, item, ByItem);
|
||||
if (k != end(data.downloaded)) {
|
||||
const auto document = k->object->document;
|
||||
files.emplace(k->path, DocumentDescriptor{
|
||||
.sessionUniqueId = id.sessionUniqueId,
|
||||
.documentId = document ? document->id : DocumentId(),
|
||||
.itemId = id.itemId,
|
||||
});
|
||||
_loaded.remove(item);
|
||||
_generated.remove(item);
|
||||
if (document) {
|
||||
_generatedDocuments.remove(document);
|
||||
}
|
||||
data.downloaded.erase(k);
|
||||
_loadedRemoved.fire_copy(item);
|
||||
|
||||
sessions.emplace(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto session : sessions) {
|
||||
writePostponed(session);
|
||||
}
|
||||
crl::async([files = std::move(files)] {
|
||||
for (const auto &[path, descriptor] : files) {
|
||||
QFile(path).remove();
|
||||
crl::on_main([descriptor] {
|
||||
if (const auto session = SessionByUniqueId(
|
||||
descriptor.sessionUniqueId)) {
|
||||
if (const auto id = descriptor.documentId) {
|
||||
[[maybe_unused]] const auto location
|
||||
= session->data().document(id)->location(true);
|
||||
}
|
||||
const auto itemId = descriptor.itemId;
|
||||
if (const auto item = session->data().message(itemId)) {
|
||||
session->data().requestItemRepaint(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto DownloadManager::loadingList() const
|
||||
-> ranges::any_view<const DownloadingId*, ranges::category::input> {
|
||||
return ranges::views::all(
|
||||
|
|
|
@ -87,6 +87,8 @@ public:
|
|||
const QString &path,
|
||||
DownloadDate started);
|
||||
|
||||
void deleteFiles(const std::vector<GlobalMsgId> &ids);
|
||||
|
||||
[[nodiscard]] auto loadingList() const
|
||||
-> ranges::any_view<const DownloadingId*, ranges::category::input>;
|
||||
[[nodiscard]] DownloadProgress loadingProgress() const;
|
||||
|
|
|
@ -1308,20 +1308,28 @@ HistoryItem::~HistoryItem() {
|
|||
applyTTL(0);
|
||||
}
|
||||
|
||||
HistoryItem *MessageByGlobalId(GlobalMsgId globalId) {
|
||||
if (!globalId.sessionUniqueId || !globalId.itemId) {
|
||||
Main::Session *SessionByUniqueId(uint64 sessionUniqueId) {
|
||||
if (!sessionUniqueId) {
|
||||
return nullptr;
|
||||
}
|
||||
for (const auto &[index, account] : Core::App().domain().accounts()) {
|
||||
if (const auto session = account->maybeSession()) {
|
||||
if (session->uniqueId() == globalId.sessionUniqueId) {
|
||||
return session->data().message(globalId.itemId);
|
||||
if (session->uniqueId() == sessionUniqueId) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HistoryItem *MessageByGlobalId(GlobalMsgId globalId) {
|
||||
const auto sessionId = globalId.itemId ? globalId.sessionUniqueId : 0;
|
||||
if (const auto session = SessionByUniqueId(sessionId)) {
|
||||
return session->data().message(globalId.itemId);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
|
||||
return base::unixtime::parse(item->date());
|
||||
}
|
||||
|
|
|
@ -495,6 +495,7 @@ private:
|
|||
|
||||
};
|
||||
|
||||
[[nodiscard]] Main::Session *SessionByUniqueId(uint64 sessionUniqueId);
|
||||
[[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId);
|
||||
|
||||
[[nodiscard]] QDateTime ItemDateTime(not_null<const HistoryItem*> item);
|
||||
|
|
|
@ -148,8 +148,8 @@ rpl::producer<SelectedItems> InnerWidget::selectedListValue() const {
|
|||
) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
void InnerWidget::cancelSelection() {
|
||||
_list->cancelSelection();
|
||||
void InnerWidget::selectionAction(SelectionAction action) {
|
||||
_list->selectionAction(action);
|
||||
}
|
||||
|
||||
InnerWidget::~InnerWidget() = default;
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace Info {
|
|||
|
||||
class Controller;
|
||||
struct SelectedItems;
|
||||
enum class SelectionAction;
|
||||
|
||||
namespace Media {
|
||||
class ListWidget;
|
||||
|
@ -46,7 +47,7 @@ public:
|
|||
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<SelectedItems> selectedListValue() const;
|
||||
void cancelSelection();
|
||||
void selectionAction(SelectionAction action);
|
||||
|
||||
~InnerWidget();
|
||||
|
||||
|
|
|
@ -335,13 +335,33 @@ std::unique_ptr<BaseLayout> Provider::createLayout(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
ListItemSelectionData Provider::computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) {
|
||||
auto result = ListItemSelectionData(selection);
|
||||
result.canDelete = true;
|
||||
result.canForward = item->allowsForward()
|
||||
&& (&item->history()->session() == &_controller->session());
|
||||
return result;
|
||||
}
|
||||
|
||||
void Provider::applyDragSelection(
|
||||
ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
bool skipFrom,
|
||||
not_null<const HistoryItem*> tillItem,
|
||||
bool skipTill) {
|
||||
// #TODO downloads
|
||||
// #TODO downloads selection
|
||||
}
|
||||
|
||||
bool Provider::allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<QString> Provider::deleteMenuPhrase() {
|
||||
return u"Delete from disk"_q;
|
||||
}
|
||||
|
||||
void Provider::saveState(
|
||||
|
|
|
@ -53,6 +53,9 @@ public:
|
|||
not_null<const HistoryItem*> a,
|
||||
not_null<const HistoryItem*> b) override;
|
||||
|
||||
Media::ListItemSelectionData computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) override;
|
||||
void applyDragSelection(
|
||||
Media::ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
|
@ -60,6 +63,11 @@ public:
|
|||
not_null<const HistoryItem*> tillItem,
|
||||
bool skipTill) override;
|
||||
|
||||
bool allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) override;
|
||||
std::optional<QString> deleteMenuPhrase() override;
|
||||
|
||||
void saveState(
|
||||
not_null<Media::Memento*> memento,
|
||||
Media::ListScrollTopState scrollState) override;
|
||||
|
|
|
@ -93,6 +93,14 @@ void Widget::restoreState(not_null<Memento*> memento) {
|
|||
scrollTopRestore(memento->scrollTop());
|
||||
}
|
||||
|
||||
rpl::producer<SelectedItems> Widget::selectedListValue() const {
|
||||
return _inner->selectedListValue();
|
||||
}
|
||||
|
||||
void Widget::selectionAction(SelectionAction action) {
|
||||
_inner->selectionAction(action);
|
||||
}
|
||||
|
||||
std::shared_ptr<Info::Memento> Make(not_null<UserData*> self) {
|
||||
return std::make_shared<Info::Memento>(
|
||||
std::vector<std::shared_ptr<ContentMemento>>(
|
||||
|
|
|
@ -54,6 +54,9 @@ public:
|
|||
const QRect &geometry,
|
||||
not_null<Memento*> memento);
|
||||
|
||||
rpl::producer<SelectedItems> selectedListValue() const override;
|
||||
void selectionAction(SelectionAction action) override;
|
||||
|
||||
private:
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
QRect floatPlayerAvailableRect() const;
|
||||
|
||||
virtual rpl::producer<SelectedItems> selectedListValue() const;
|
||||
virtual void cancelSelection() {
|
||||
virtual void selectionAction(SelectionAction action) {
|
||||
}
|
||||
|
||||
virtual void saveChanges(FnMut<void()> done);
|
||||
|
|
|
@ -303,7 +303,7 @@ rpl::producer<bool> Controller::searchEnabledByContent() const {
|
|||
rpl::producer<QString> Controller::mediaSourceQueryValue() const {
|
||||
return _searchController
|
||||
? _searchController->currentQueryValue()
|
||||
: rpl::single(QString()); // #TODO downloads
|
||||
: rpl::single(QString()); // #TODO downloads search
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(
|
||||
|
|
|
@ -434,8 +434,8 @@ SelectedItems TopBar::takeSelectedItems() {
|
|||
return std::move(_selectedItems);
|
||||
}
|
||||
|
||||
rpl::producer<> TopBar::cancelSelectionRequests() const {
|
||||
return _cancelSelectionClicks.events();
|
||||
rpl::producer<SelectionAction> TopBar::selectionActionRequests() const {
|
||||
return _selectionActionRequests.events();
|
||||
}
|
||||
|
||||
void TopBar::updateSelectionState() {
|
||||
|
@ -466,9 +466,10 @@ void TopBar::createSelectionControls() {
|
|||
st::infoTopBarScale));
|
||||
_cancelSelection->setDuration(st::infoTopBarDuration);
|
||||
_cancelSelection->entity()->clicks(
|
||||
) | rpl::to_empty
|
||||
| rpl::start_to_stream(
|
||||
_cancelSelectionClicks,
|
||||
) | rpl::map_to(
|
||||
SelectionAction::Clear
|
||||
) | rpl::start_to_stream(
|
||||
_selectionActionRequests,
|
||||
_cancelSelection->lifetime());
|
||||
_selectionText = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::LabelWithNumbers>>(
|
||||
this,
|
||||
|
@ -488,7 +489,12 @@ void TopBar::createSelectionControls() {
|
|||
_forward.data(),
|
||||
[this] { return selectionMode() && _canForward; });
|
||||
_forward->setDuration(st::infoTopBarDuration);
|
||||
_forward->entity()->addClickHandler([this] { performForward(); });
|
||||
_forward->entity()->clicks(
|
||||
) | rpl::map_to(
|
||||
SelectionAction::Forward
|
||||
) | rpl::start_to_stream(
|
||||
_selectionActionRequests,
|
||||
_cancelSelection->lifetime());
|
||||
_forward->entity()->setVisible(_canForward);
|
||||
_delete = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
|
||||
this,
|
||||
|
@ -498,7 +504,12 @@ void TopBar::createSelectionControls() {
|
|||
_delete.data(),
|
||||
[this] { return selectionMode() && _canDelete; });
|
||||
_delete->setDuration(st::infoTopBarDuration);
|
||||
_delete->entity()->addClickHandler([this] { performDelete(); });
|
||||
_delete->entity()->clicks(
|
||||
) | rpl::map_to(
|
||||
SelectionAction::Delete
|
||||
) | rpl::start_to_stream(
|
||||
_selectionActionRequests,
|
||||
_cancelSelection->lifetime());
|
||||
_delete->entity()->setVisible(_canDelete);
|
||||
|
||||
updateControlsGeometry(width());
|
||||
|
@ -541,50 +552,12 @@ bool TopBar::searchMode() const {
|
|||
return _searchModeAvailable && _searchModeEnabled;
|
||||
}
|
||||
|
||||
MessageIdsList TopBar::collectItems() const {
|
||||
return ranges::views::all(
|
||||
_selectedItems.list
|
||||
) | ranges::views::transform([](auto &&item) {
|
||||
return item.globalId;
|
||||
}) | ranges::views::filter([&](const GlobalMsgId &globalId) {
|
||||
const auto session = &_navigation->session();
|
||||
return (globalId.sessionUniqueId == session->uniqueId())
|
||||
&& (session->data().message(globalId.itemId) != nullptr);
|
||||
}) | ranges::views::transform([](const GlobalMsgId &globalId) {
|
||||
return globalId.itemId;
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
void TopBar::performForward() {
|
||||
auto items = collectItems();
|
||||
if (items.empty()) {
|
||||
_cancelSelectionClicks.fire({});
|
||||
return;
|
||||
}
|
||||
Window::ShowForwardMessagesBox(
|
||||
_navigation,
|
||||
std::move(items),
|
||||
[weak = Ui::MakeWeak(this)] {
|
||||
if (weak) {
|
||||
weak->_cancelSelectionClicks.fire({});
|
||||
}
|
||||
});
|
||||
_selectionActionRequests.fire(SelectionAction::Forward);
|
||||
}
|
||||
|
||||
void TopBar::performDelete() {
|
||||
// #TODO downloads
|
||||
auto items = collectItems();
|
||||
if (items.empty()) {
|
||||
_cancelSelectionClicks.fire({});
|
||||
} else {
|
||||
auto box = Box<DeleteMessagesBox>(
|
||||
&_navigation->session(),
|
||||
std::move(items));
|
||||
box->setDeleteConfirmedCallback(crl::guard(this, [=] {
|
||||
_cancelSelectionClicks.fire({});
|
||||
}));
|
||||
_navigation->parentController()->show(std::move(box));
|
||||
}
|
||||
_selectionActionRequests.fire(SelectionAction::Delete);
|
||||
}
|
||||
|
||||
rpl::producer<QString> TitleValue(
|
||||
|
|
|
@ -83,7 +83,8 @@ public:
|
|||
void setSelectedItems(SelectedItems &&items);
|
||||
SelectedItems takeSelectedItems();
|
||||
|
||||
rpl::producer<> cancelSelectionRequests() const;
|
||||
[[nodiscard]] auto selectionActionRequests() const
|
||||
-> rpl::producer<SelectionAction>;
|
||||
|
||||
void finishAnimating() {
|
||||
updateControlsVisibility(anim::type::instant);
|
||||
|
@ -115,9 +116,7 @@ private:
|
|||
[[nodiscard]] bool computeCanForward() const;
|
||||
void updateSelectionState();
|
||||
void createSelectionControls();
|
||||
void clearSelectionControls();
|
||||
|
||||
MessageIdsList collectItems() const;
|
||||
void performForward();
|
||||
void performDelete();
|
||||
|
||||
|
@ -161,7 +160,7 @@ private:
|
|||
QPointer<Ui::FadeWrap<Ui::LabelWithNumbers>> _selectionText;
|
||||
QPointer<Ui::FadeWrap<Ui::IconButton>> _forward;
|
||||
QPointer<Ui::FadeWrap<Ui::IconButton>> _delete;
|
||||
rpl::event_stream<> _cancelSelectionClicks;
|
||||
rpl::event_stream<SelectionAction> _selectionActionRequests;
|
||||
|
||||
using UpdateCallback = Fn<bool(anim::type)>;
|
||||
std::map<QObject*, UpdateCallback> _updateControlCallbacks;
|
||||
|
|
|
@ -343,9 +343,9 @@ void WrapWidget::createTopBar() {
|
|||
_controller.get(),
|
||||
TopBarStyle(wrapValue),
|
||||
std::move(selectedItems));
|
||||
_topBar->cancelSelectionRequests(
|
||||
) | rpl::start_with_next([this] {
|
||||
_content->cancelSelection();
|
||||
_topBar->selectionActionRequests(
|
||||
) | rpl::start_with_next([=](SelectionAction action) {
|
||||
_content->selectionAction(action);
|
||||
}, _topBar->lifetime());
|
||||
|
||||
_topBar->setTitle(TitleValue(
|
||||
|
|
|
@ -66,7 +66,12 @@ struct SelectedItems {
|
|||
|
||||
Storage::SharedMediaType type;
|
||||
std::vector<SelectedItem> list;
|
||||
};
|
||||
|
||||
enum class SelectionAction {
|
||||
Clear,
|
||||
Forward,
|
||||
Delete,
|
||||
};
|
||||
|
||||
class WrapWidget final : public Window::SectionWidget {
|
||||
|
|
|
@ -28,22 +28,19 @@ UniversalMsgId GetUniversalId(not_null<const BaseLayout*> layout) {
|
|||
bool ChangeItemSelection(
|
||||
ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) {
|
||||
ListItemSelectionData selectionData) {
|
||||
const auto changeExisting = [&](auto it) {
|
||||
if (it == selected.cend()) {
|
||||
return false;
|
||||
} else if (it->second.text != selection) {
|
||||
it->second.text = selection;
|
||||
} else if (it->second != selectionData) {
|
||||
it->second = selectionData;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (selected.size() < MaxSelectedItems) {
|
||||
const auto [i, ok] = selected.try_emplace(item, selection);
|
||||
const auto [i, ok] = selected.try_emplace(item, selectionData);
|
||||
if (ok) {
|
||||
// #TODO downloads
|
||||
i->second.canDelete = item->canDelete();
|
||||
i->second.canForward = item->allowsForward();
|
||||
return true;
|
||||
}
|
||||
return changeExisting(i);
|
||||
|
|
|
@ -32,6 +32,14 @@ struct ListItemSelectionData {
|
|||
bool canForward = false;
|
||||
};
|
||||
|
||||
inline bool operator==(
|
||||
ListItemSelectionData a,
|
||||
ListItemSelectionData b) {
|
||||
return (a.text == b.text)
|
||||
&& (a.canDelete == b.canDelete)
|
||||
&& (a.canForward == b.canForward);
|
||||
}
|
||||
|
||||
using ListSelectedMap = base::flat_map<
|
||||
not_null<const HistoryItem*>,
|
||||
ListItemSelectionData,
|
||||
|
@ -83,7 +91,7 @@ using UniversalMsgId = MsgId;
|
|||
bool ChangeItemSelection(
|
||||
ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection);
|
||||
ListItemSelectionData selectionData);
|
||||
|
||||
class ListSectionDelegate {
|
||||
public:
|
||||
|
@ -132,6 +140,9 @@ public:
|
|||
not_null<const HistoryItem*> a,
|
||||
not_null<const HistoryItem*> b) = 0;
|
||||
|
||||
[[nodiscard]] virtual ListItemSelectionData computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) = 0;
|
||||
virtual void applyDragSelection(
|
||||
ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
|
@ -139,6 +150,11 @@ public:
|
|||
not_null<const HistoryItem*> tillItem,
|
||||
bool skipTill) = 0;
|
||||
|
||||
[[nodiscard]] virtual bool allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) = 0;
|
||||
[[nodiscard]] virtual std::optional<QString> deleteMenuPhrase() = 0;
|
||||
|
||||
virtual void saveState(
|
||||
not_null<Memento*> memento,
|
||||
ListScrollTopState scrollState) = 0;
|
||||
|
|
|
@ -281,8 +281,8 @@ rpl::producer<SelectedItems> InnerWidget::selectedListValue() const {
|
|||
) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
void InnerWidget::cancelSelection() {
|
||||
_list->cancelSelection();
|
||||
void InnerWidget::selectionAction(SelectionAction action) {
|
||||
_list->selectionAction(action);
|
||||
}
|
||||
|
||||
InnerWidget::~InnerWidget() = default;
|
||||
|
|
|
@ -48,7 +48,7 @@ public:
|
|||
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<SelectedItems> selectedListValue() const;
|
||||
void cancelSelection();
|
||||
void selectionAction(SelectionAction action);
|
||||
|
||||
~InnerWidget();
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_download_manager.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
|
@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_session_controller.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/delete_message_context_action.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
|
@ -46,9 +48,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/delete_messages_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/application.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_overview.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
@ -215,6 +219,14 @@ rpl::producer<SelectedItems> ListWidget::selectedListValue() const {
|
|||
collectSelectedItems());
|
||||
}
|
||||
|
||||
void ListWidget::selectionAction(SelectionAction action) {
|
||||
switch (action) {
|
||||
case SelectionAction::Clear: clearSelected(); return;
|
||||
case SelectionAction::Forward: forwardSelected(); return;
|
||||
case SelectionAction::Delete: deleteSelected(); return;
|
||||
}
|
||||
}
|
||||
|
||||
QRect ListWidget::getCurrentSongGeometry() {
|
||||
const auto type = AudioMsgId::Type::Song;
|
||||
const auto current = ::Media::Player::instance()->current(type);
|
||||
|
@ -307,11 +319,21 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems {
|
|||
}
|
||||
|
||||
MessageIdsList ListWidget::collectSelectedIds() const {
|
||||
const auto selected = collectSelectedItems();
|
||||
return collectSelectedIds(collectSelectedItems());
|
||||
}
|
||||
|
||||
MessageIdsList ListWidget::collectSelectedIds(
|
||||
const SelectedItems &items) const {
|
||||
const auto session = &_controller->session();
|
||||
return ranges::views::all(
|
||||
selected.list
|
||||
) | ranges::views::transform([](const SelectedItem &item) {
|
||||
return item.globalId.itemId; // #TODO downloads
|
||||
items.list
|
||||
) | ranges::views::transform([](auto &&item) {
|
||||
return item.globalId;
|
||||
}) | ranges::views::filter([&](const GlobalMsgId &globalId) {
|
||||
return (globalId.sessionUniqueId == session->uniqueId())
|
||||
&& (session->data().message(globalId.itemId) != nullptr);
|
||||
}) | ranges::views::transform([](const GlobalMsgId &globalId) {
|
||||
return globalId.itemId;
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
|
@ -806,19 +828,20 @@ void ListWidget::showContextMenu(
|
|||
|
||||
auto link = ClickHandler::getActive();
|
||||
|
||||
const auto itemFullId = item->fullId();
|
||||
const auto owner = &session().data();
|
||||
_contextMenu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
_contextMenu->addAction(
|
||||
tr::lng_context_to_msg(tr::now),
|
||||
[=] {
|
||||
if (const auto item = owner->message(itemFullId)) {
|
||||
_controller->parentController()->showPeerHistoryAtItem(item);
|
||||
}
|
||||
},
|
||||
&st::menuIconShowInChat);
|
||||
if (item->isHistoryEntry()) {
|
||||
_contextMenu->addAction(
|
||||
tr::lng_context_to_msg(tr::now),
|
||||
[=] {
|
||||
if (const auto item = MessageByGlobalId(globalId)) {
|
||||
goToMessageClickHandler(item)->onClick({});
|
||||
}
|
||||
},
|
||||
&st::menuIconShowInChat);
|
||||
}
|
||||
|
||||
const auto lnkPhoto = link
|
||||
? reinterpret_cast<PhotoData*>(
|
||||
|
@ -870,12 +893,11 @@ void ListWidget::showContextMenu(
|
|||
this,
|
||||
[=] {
|
||||
DocumentSaveClickHandler::Save(
|
||||
itemFullId,
|
||||
globalId.itemId,
|
||||
lnkDocument,
|
||||
DocumentSaveClickHandler::Mode::ToNewFile);
|
||||
});
|
||||
if (item->history()->peer->allowsForwarding()
|
||||
&& !item->forbidsForward()) {
|
||||
if (_provider->allowSaveFileAs(item, lnkDocument)) {
|
||||
_contextMenu->addAction(
|
||||
(isVideo
|
||||
? tr::lng_context_save_video(tr::now)
|
||||
|
@ -911,7 +933,9 @@ void ListWidget::showContextMenu(
|
|||
}
|
||||
if (canDeleteAll()) {
|
||||
_contextMenu->addAction(
|
||||
tr::lng_context_delete_selected(tr::now),
|
||||
(_controller->isDownloads()
|
||||
? u"Delete from disk"_q
|
||||
: tr::lng_context_delete_selected(tr::now)),
|
||||
crl::guard(this, [this] {
|
||||
deleteSelected();
|
||||
}),
|
||||
|
@ -925,18 +949,28 @@ void ListWidget::showContextMenu(
|
|||
&st::menuIconSelect);
|
||||
} else {
|
||||
if (overSelected != SelectionState::NotOverSelectedItems) {
|
||||
if (item->allowsForward()) {
|
||||
const auto selectionData = _provider->computeSelectionData(
|
||||
item,
|
||||
FullSelection);
|
||||
if (selectionData.canForward) {
|
||||
_contextMenu->addAction(
|
||||
tr::lng_context_forward_msg(tr::now),
|
||||
crl::guard(this, [=] { forwardItem(globalId); }),
|
||||
&st::menuIconForward);
|
||||
}
|
||||
if (item->canDelete()) {
|
||||
_contextMenu->addAction(Ui::DeleteMessageContextAction(
|
||||
_contextMenu->menu(),
|
||||
crl::guard(this, [=] { deleteItem(globalId); }),
|
||||
item->ttlDestroyAt(),
|
||||
[=] { _contextMenu = nullptr; }));
|
||||
if (selectionData.canDelete) {
|
||||
if (_controller->isDownloads()) {
|
||||
_contextMenu->addAction(
|
||||
u"Delete from disk"_q,
|
||||
crl::guard(this, [=] { deleteItem(globalId); }),
|
||||
&st::menuIconDelete);
|
||||
} else {
|
||||
_contextMenu->addAction(Ui::DeleteMessageContextAction(
|
||||
_contextMenu->menu(),
|
||||
crl::guard(this, [=] { deleteItem(globalId); }),
|
||||
item->ttlDestroyAt(),
|
||||
[=] { _contextMenu = nullptr; }));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_provider->hasSelectRestriction()) {
|
||||
|
@ -1005,36 +1039,62 @@ void ListWidget::forwardItems(MessageIdsList &&items) {
|
|||
}
|
||||
|
||||
void ListWidget::deleteSelected() {
|
||||
if (const auto box = deleteItems(collectSelectedIds())) {
|
||||
box->setDeleteConfirmedCallback(crl::guard(this, [=]{
|
||||
clearSelected();
|
||||
}));
|
||||
}
|
||||
deleteItems(collectSelectedItems(), crl::guard(this, [=]{
|
||||
clearSelected();
|
||||
}));
|
||||
}
|
||||
|
||||
void ListWidget::deleteItem(GlobalMsgId globalId) {
|
||||
const auto session = &_controller->session();
|
||||
if (globalId.sessionUniqueId == session->uniqueId()) {
|
||||
if (const auto item = session->data().message(globalId.itemId)) {
|
||||
deleteItems({ 1, item->fullId() });
|
||||
if (const auto item = MessageByGlobalId(globalId)) {
|
||||
auto items = SelectedItems(_provider->type());
|
||||
items.list.push_back(SelectedItem(item->globalId()));
|
||||
const auto selectionData = _provider->computeSelectionData(
|
||||
item,
|
||||
FullSelection);
|
||||
items.list.back().canDelete = selectionData.canDelete;
|
||||
items.list.back().canForward = selectionData.canForward;
|
||||
deleteItems(std::move(items));
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::deleteItems(SelectedItems &&items, Fn<void()> confirmed) {
|
||||
const auto window = _controller->parentController();
|
||||
if (items.list.empty()) {
|
||||
return;
|
||||
} else if (_controller->isDownloads()) {
|
||||
const auto phrase = (items.list.size() == 1)
|
||||
? u"Do you want to delete this file?"_q
|
||||
: u"Do you want to delete X files?"_q;
|
||||
const auto deleteSure = [=] {
|
||||
const auto ids = ranges::views::all(
|
||||
items.list
|
||||
) | ranges::views::transform([](const SelectedItem &item) {
|
||||
return item.globalId;
|
||||
}) | ranges::to_vector;
|
||||
Core::App().downloadManager().deleteFiles(ids);
|
||||
if (const auto box = _actionBoxWeak.data()) {
|
||||
box->closeBox();
|
||||
}
|
||||
};
|
||||
setActionBoxWeak(window->show(Box<Ui::ConfirmBox>(
|
||||
phrase,
|
||||
tr::lng_box_delete(tr::now),
|
||||
st::attentionBoxButton,
|
||||
deleteSure)));
|
||||
} else if (auto list = collectSelectedIds(items); !list.empty()) {
|
||||
auto box = Box<DeleteMessagesBox>(
|
||||
&_controller->session(),
|
||||
std::move(list));
|
||||
const auto weak = box.data();
|
||||
window->show(std::move(box));
|
||||
setActionBoxWeak(weak);
|
||||
if (confirmed) {
|
||||
weak->setDeleteConfirmedCallback(std::move(confirmed));
|
||||
}
|
||||
}
|
||||
// #TODO downloads
|
||||
}
|
||||
|
||||
DeleteMessagesBox *ListWidget::deleteItems(MessageIdsList &&items) {
|
||||
if (!items.empty()) {
|
||||
const auto box = Ui::show(
|
||||
Box<DeleteMessagesBox>(
|
||||
&_controller->session(),
|
||||
std::move(items))).data();
|
||||
setActionBoxWeak(box);
|
||||
return box;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ListWidget::setActionBoxWeak(QPointer<Ui::RpWidget> box) {
|
||||
void ListWidget::setActionBoxWeak(QPointer<Ui::BoxContent> box) {
|
||||
if ((_actionBoxWeak = box)) {
|
||||
_actionBoxWeakLifetime = _actionBoxWeak->alive(
|
||||
) | rpl::start_with_done([weak = Ui::MakeWeak(this)]{
|
||||
|
@ -1087,7 +1147,11 @@ void ListWidget::switchToWordSelection() {
|
|||
void ListWidget::applyItemSelection(
|
||||
HistoryItem *item,
|
||||
TextSelection selection) {
|
||||
if (item && ChangeItemSelection(_selected, item, selection)) {
|
||||
if (item
|
||||
&& ChangeItemSelection(
|
||||
_selected,
|
||||
item,
|
||||
_provider->computeSelectionData(item, selection))) {
|
||||
repaintItem(item);
|
||||
pushSelectedItems();
|
||||
}
|
||||
|
@ -1624,7 +1688,10 @@ void ListWidget::applyDragSelection() {
|
|||
void ListWidget::applyDragSelection(SelectedMap &applyTo) const {
|
||||
if (_dragSelectAction == DragSelectAction::Selecting) {
|
||||
for (auto &[item, data] : _dragSelected) {
|
||||
ChangeItemSelection(applyTo, item, FullSelection);
|
||||
ChangeItemSelection(
|
||||
applyTo,
|
||||
item,
|
||||
_provider->computeSelectionData(item, FullSelection));
|
||||
}
|
||||
} else if (_dragSelectAction == DragSelectAction::Deselecting) {
|
||||
for (auto &[item, data] : _dragSelected) {
|
||||
|
|
|
@ -27,6 +27,7 @@ enum class PointState : char;
|
|||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
class BoxContent;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Overview {
|
||||
|
@ -65,9 +66,7 @@ public:
|
|||
|
||||
rpl::producer<int> scrollToRequests() const;
|
||||
rpl::producer<SelectedItems> selectedListValue() const;
|
||||
void cancelSelection() {
|
||||
clearSelected();
|
||||
}
|
||||
void selectionAction(SelectionAction action);
|
||||
|
||||
QRect getCurrentSongGeometry();
|
||||
rpl::producer<> checkForHide() const {
|
||||
|
@ -152,7 +151,6 @@ private:
|
|||
void setupSelectRestriction();
|
||||
|
||||
QMargins padding() const;
|
||||
bool isMyItem(not_null<const HistoryItem*> item) const;
|
||||
bool isItemLayout(
|
||||
not_null<const HistoryItem*> item,
|
||||
BaseLayout *layout) const;
|
||||
|
@ -168,6 +166,8 @@ private:
|
|||
|
||||
[[nodiscard]] SelectedItems collectSelectedItems() const;
|
||||
[[nodiscard]] MessageIdsList collectSelectedIds() const;
|
||||
[[nodiscard]] MessageIdsList collectSelectedIds(
|
||||
const SelectedItems &items) const;
|
||||
void pushSelectedItems();
|
||||
[[nodiscard]] bool hasSelected() const;
|
||||
[[nodiscard]] bool isSelectedItem(
|
||||
|
@ -182,7 +182,7 @@ private:
|
|||
void forwardItems(MessageIdsList &&items);
|
||||
void deleteSelected();
|
||||
void deleteItem(GlobalMsgId globalId);
|
||||
DeleteMessagesBox *deleteItems(MessageIdsList &&items);
|
||||
void deleteItems(SelectedItems &&items, Fn<void()> confirmed = nullptr);
|
||||
void applyItemSelection(
|
||||
HistoryItem *item,
|
||||
TextSelection selection);
|
||||
|
@ -254,7 +254,7 @@ private:
|
|||
void checkMoveToOtherViewer();
|
||||
void clearHeavyItems();
|
||||
|
||||
void setActionBoxWeak(QPointer<Ui::RpWidget> box);
|
||||
void setActionBoxWeak(QPointer<Ui::BoxContent> box);
|
||||
|
||||
const not_null<AbstractController*> _controller;
|
||||
const std::unique_ptr<ListProvider> _provider;
|
||||
|
@ -290,7 +290,7 @@ private:
|
|||
|
||||
base::unique_qptr<Ui::PopupMenu> _contextMenu;
|
||||
rpl::event_stream<> _checkForHide;
|
||||
QPointer<Ui::RpWidget> _actionBoxWeak;
|
||||
QPointer<Ui::BoxContent> _actionBoxWeak;
|
||||
rpl::lifetime _actionBoxWeakLifetime;
|
||||
|
||||
QPoint _trippleClickPoint;
|
||||
|
|
|
@ -461,6 +461,25 @@ std::unique_ptr<BaseLayout> Provider::createLayout(
|
|||
Unexpected("Type in ListWidget::createLayout()");
|
||||
}
|
||||
|
||||
ListItemSelectionData Provider::computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) {
|
||||
auto result = ListItemSelectionData(selection);
|
||||
result.canDelete = item->canDelete();
|
||||
result.canForward = item->allowsForward();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Provider::allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) {
|
||||
return item->allowsForward();
|
||||
}
|
||||
|
||||
std::optional<QString> Provider::deleteMenuPhrase() {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Provider::applyDragSelection(
|
||||
ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
|
@ -480,10 +499,11 @@ void Provider::applyDragSelection(
|
|||
for (auto &layoutItem : _layouts) {
|
||||
auto &&universalId = layoutItem.first;
|
||||
if (universalId <= fromId && universalId > tillId) {
|
||||
const auto item = layoutItem.second.item->getItem();
|
||||
ChangeItemSelection(
|
||||
selected,
|
||||
layoutItem.second.item->getItem(),
|
||||
FullSelection);
|
||||
item,
|
||||
computeSelectionData(item, FullSelection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,9 @@ public:
|
|||
not_null<const HistoryItem*> a,
|
||||
not_null<const HistoryItem*> b) override;
|
||||
|
||||
ListItemSelectionData computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) override;
|
||||
void applyDragSelection(
|
||||
ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
|
@ -53,6 +56,11 @@ public:
|
|||
not_null<const HistoryItem*> tillItem,
|
||||
bool skipTill) override;
|
||||
|
||||
bool allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) override;
|
||||
std::optional<QString> deleteMenuPhrase() override;
|
||||
|
||||
void saveState(
|
||||
not_null<Memento*> memento,
|
||||
ListScrollTopState scrollState) override;
|
||||
|
|
|
@ -93,8 +93,8 @@ rpl::producer<SelectedItems> Widget::selectedListValue() const {
|
|||
return _inner->selectedListValue();
|
||||
}
|
||||
|
||||
void Widget::cancelSelection() {
|
||||
_inner->cancelSelection();
|
||||
void Widget::selectionAction(SelectionAction action) {
|
||||
_inner->selectionAction(action);
|
||||
}
|
||||
|
||||
void Widget::setIsStackBottom(bool isStackBottom) {
|
||||
|
|
|
@ -99,7 +99,7 @@ public:
|
|||
not_null<Memento*> memento);
|
||||
|
||||
rpl::producer<SelectedItems> selectedListValue() const override;
|
||||
void cancelSelection() override;
|
||||
void selectionAction(SelectionAction action) override;
|
||||
|
||||
private:
|
||||
void saveState(not_null<Memento*> memento);
|
||||
|
|
|
@ -679,7 +679,7 @@ void InnerWidget::setupContent() {
|
|||
st::boxRowPadding.right(),
|
||||
st::boxMediumSkip });
|
||||
for (const auto &answer : _poll->answers) {
|
||||
const auto session = &_controller->parentController()->session();
|
||||
const auto session = &_controller->session();
|
||||
const auto controller = CreateAnswerRows(
|
||||
_content,
|
||||
_visibleTop.value(),
|
||||
|
|
|
@ -924,7 +924,7 @@ Document::Document(
|
|||
const style::OverviewFileLayout &st)
|
||||
: RadialProgressItem(delegate, parent)
|
||||
, _data(fields.document)
|
||||
, _msgl(goToMessageClickHandler(parent))
|
||||
, _msgl(parent->isHistoryEntry() ? goToMessageClickHandler(parent) : nullptr)
|
||||
, _namel(std::make_shared<DocumentOpenClickHandler>(
|
||||
_data,
|
||||
crl::guard(this, [=](FullMsgId id) {
|
||||
|
@ -1189,7 +1189,9 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con
|
|||
p.drawTextLeft(nameleft, statustop, _width, _status.text());
|
||||
}
|
||||
if (datetop >= 0 && clip.intersects(style::rtlrect(nameleft, datetop, _datew, st::normalFont->height, _width))) {
|
||||
p.setFont(ClickHandler::showAsActive(_msgl) ? st::normalFont->underline() : st::normalFont);
|
||||
p.setFont((_msgl && ClickHandler::showAsActive(_msgl))
|
||||
? st::normalFont->underline()
|
||||
: st::normalFont);
|
||||
p.setPen(st::mediaInFg);
|
||||
p.drawTextLeft(nameleft, datetop, _width, _date, _datew);
|
||||
}
|
||||
|
|
|
@ -106,7 +106,14 @@ void ActionWithTimer::paint(Painter &p) {
|
|||
paintRipple(p, 0, 0);
|
||||
}
|
||||
|
||||
st::menuIconDelete.paint(p, _st.itemIconPosition, width());
|
||||
const auto normalHeight = _st.itemPadding.top()
|
||||
+ _st.itemStyle.font->height
|
||||
+ _st.itemPadding.bottom();
|
||||
const auto deltaHeight = _height - normalHeight;
|
||||
st::menuIconDelete.paint(
|
||||
p,
|
||||
_st.itemIconPosition + QPoint(0, deltaHeight / 2),
|
||||
width());
|
||||
|
||||
p.setPen(selected ? _st.itemFgOver : _st.itemFg);
|
||||
_text.drawLeftElided(
|
||||
|
|
|
@ -1083,7 +1083,7 @@ void BlockSenderFromRepliesBox(
|
|||
Window::ClearReply{ id });
|
||||
}
|
||||
|
||||
QPointer<Ui::RpWidget> ShowForwardMessagesBox(
|
||||
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
Data::ForwardDraft &&draft,
|
||||
FnMut<void()> &&successCallback) {
|
||||
|
@ -1130,7 +1130,7 @@ QPointer<Ui::RpWidget> ShowForwardMessagesBox(
|
|||
return weak->data();
|
||||
}
|
||||
|
||||
QPointer<Ui::RpWidget> ShowForwardMessagesBox(
|
||||
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
MessageIdsList &&items,
|
||||
FnMut<void()> &&successCallback) {
|
||||
|
@ -1140,7 +1140,7 @@ QPointer<Ui::RpWidget> ShowForwardMessagesBox(
|
|||
std::move(successCallback));
|
||||
}
|
||||
|
||||
QPointer<Ui::RpWidget> ShowSendNowMessagesBox(
|
||||
QPointer<Ui::BoxContent> ShowSendNowMessagesBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<History*> history,
|
||||
MessageIdsList &&items,
|
||||
|
|
|
@ -15,6 +15,7 @@ class History;
|
|||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
class BoxContent;
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
||||
|
@ -98,16 +99,16 @@ void ToggleHistoryArchived(not_null<History*> history, bool archived);
|
|||
Fn<void()> ClearHistoryHandler(not_null<PeerData*> peer);
|
||||
Fn<void()> DeleteAndLeaveHandler(not_null<PeerData*> peer);
|
||||
|
||||
QPointer<Ui::RpWidget> ShowForwardMessagesBox(
|
||||
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
Data::ForwardDraft &&draft,
|
||||
FnMut<void()> &&successCallback = nullptr);
|
||||
QPointer<Ui::RpWidget> ShowForwardMessagesBox(
|
||||
QPointer<Ui::BoxContent> ShowForwardMessagesBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
MessageIdsList &&items,
|
||||
FnMut<void()> &&successCallback = nullptr);
|
||||
|
||||
QPointer<Ui::RpWidget> ShowSendNowMessagesBox(
|
||||
QPointer<Ui::BoxContent> ShowSendNowMessagesBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<History*> history,
|
||||
MessageIdsList &&items,
|
||||
|
|
Loading…
Add table
Reference in a new issue