Select/forward/delete group of messages.

This commit is contained in:
John Preston 2017-12-15 21:36:28 +03:00
parent 537400d8b2
commit b6087ce7ce
5 changed files with 228 additions and 156 deletions

View File

@ -28,6 +28,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_media_types.h" #include "history/history_media_types.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/window_peer_menu.h"
#include "boxes/confirm_box.h"
#include "chat_helpers/message_field.h" #include "chat_helpers/message_field.h"
#include "chat_helpers/stickers.h" #include "chat_helpers/stickers.h"
#include "history/history_widget.h" #include "history/history_widget.h"
@ -1110,7 +1112,7 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu
&& !_pressWasInactive && !_pressWasInactive
&& !_selected.empty() && !_selected.empty()
&& (_selected.cbegin()->second == FullSelection)) { && (_selected.cbegin()->second == FullSelection)) {
changeDragSelection( changeSelectionAsGroup(
&_selected, &_selected,
_mouseActionItem, _mouseActionItem,
SelectAction::Invert); SelectAction::Invert);
@ -1220,7 +1222,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
isUponSelected = -1; isUponSelected = -1;
if (_selected.cbegin()->second == FullSelection) { if (_selected.cbegin()->second == FullSelection) {
hasSelected = 2; hasSelected = 2;
if (App::hoveredItem() && _selected.find(App::hoveredItem()) != _selected.cend()) { if (_dragStateItem && _selected.find(_dragStateItem) != _selected.cend()) {
isUponSelected = 2; isUponSelected = 2;
} else { } else {
isUponSelected = -2; isUponSelected = -2;
@ -1307,23 +1309,35 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
})); }));
} }
_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected())); _menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) { } else if (_dragStateItem) {
if (isUponSelected != -2) { if (isUponSelected != -2) {
if (App::hoveredLinkItem()->canForward()) { if (_dragStateItem->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true); _menu->addAction(lang(lng_context_forward_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
forwardItem(item);
} }
if (App::hoveredLinkItem()->canDelete()) { }))->setEnabled(true);
}
if (_dragStateItem->canDelete()) {
_menu->addAction(lang(lng_context_delete_msg), base::lambda_guarded(this, [this] { _menu->addAction(lang(lng_context_delete_msg), base::lambda_guarded(this, [this] {
_widget->confirmDeleteContextItem(); if (const auto item = App::contextItem()) {
deleteItem(item);
}
})); }));
} }
} }
if (App::hoveredLinkItem()->id > 0 && !App::hoveredLinkItem()->serviceMsg()) { if (_dragStateItem && _dragStateItem->id > 0 && !_dragStateItem->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] { _menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
// TODO if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelection(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true); }))->setEnabled(true);
} }
App::contextItem(App::hoveredLinkItem()); App::contextItem(_dragStateItem);
} }
} else { // maybe cursor on some text history item? } else { // maybe cursor on some text history item?
bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg()); bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg());
@ -1421,23 +1435,46 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
} }
_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected())); _menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
} else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) { } else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) {
const auto fullId = item->fullId();
if (isUponSelected != -2) { if (isUponSelected != -2) {
if (canForward) { if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true); _menu->addAction(lang(lng_context_forward_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
forwardAsGroup(item);
}
}))->setEnabled(true);
} }
if (canDelete) { if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), base::lambda_guarded(this, [this] { _menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), base::lambda_guarded(this, [this] {
_widget->confirmDeleteContextItem(); if (const auto item = App::contextItem()) {
deleteAsGroup(item);
}
})); }));
} }
} }
if (item->id > 0 && !item->serviceMsg()) { if (item->id > 0 && !item->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true); _menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelectionAsGroup(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
} }
} else { } else {
if (App::mousedItem() && !App::mousedItem()->serviceMsg() && App::mousedItem()->id > 0) { if (App::mousedItem() && !App::mousedItem()->serviceMsg() && App::mousedItem()->id > 0) {
_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true); _menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelectionAsGroup(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
item = App::mousedItem(); item = App::mousedItem();
} }
} }
@ -2428,110 +2465,167 @@ void HistoryInner::applyDragSelection() {
applyDragSelection(&_selected); applyDragSelection(&_selected);
} }
bool HistoryInner::isFullSelected( HistoryMessageGroup *HistoryInner::itemGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const { not_null<HistoryItem*> item) const {
const auto group = [&] {
if (const auto group = item->Get<HistoryMessageGroup>()) { if (const auto group = item->Get<HistoryMessageGroup>()) {
if (group->leader == item) { if (group->leader == item) {
return group; return group;
} }
return group->leader->Get<HistoryMessageGroup>(); return group->leader->Get<HistoryMessageGroup>();
} }
return (HistoryMessageGroup*)nullptr; return nullptr;
}(); }
const auto singleSelected = [&](not_null<HistoryItem*> item) {
bool HistoryInner::isSelected(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item); const auto i = toItems->find(item);
return (i != toItems->cend()) && (i->second == FullSelection); return (i != toItems->cend()) && (i->second == FullSelection);
}; }
if (group) {
if (!singleSelected(group->leader)) { bool HistoryInner::isSelectedAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
if (const auto group = itemGroup(item)) {
if (!isSelected(toItems, group->leader)) {
return false; return false;
} }
for (const auto other : group->others) { for (const auto other : group->others) {
if (!singleSelected(other)) { if (!isSelected(toItems, other)) {
return false; return false;
} }
} }
} else if (!singleSelected(item)) {
return false;
}
return true; return true;
} }
return isSelected(toItems, item);
}
void HistoryInner::changeDragSelection( bool HistoryInner::goodForSelection(
not_null<SelectedItems*> toItems, not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
SelectAction action) const { int &totalCount) const {
if (action == SelectAction::Invert) {
action = isFullSelected(toItems, item)
? SelectAction::Deselect
: SelectAction::Select;
}
auto total = toItems->size();
const auto add = (action == SelectAction::Select);
const auto goodForAdding = [&](not_null<HistoryItem*> item) {
if (item->id <= 0 || item->serviceMsg()) { if (item->id <= 0 || item->serviceMsg()) {
return false; return false;
} }
if (toItems->find(item) == toItems->end()) { if (toItems->find(item) == toItems->end()) {
++total; ++totalCount;
} }
return true; return true;
}; }
const auto addSingle = [&](not_null<HistoryItem*> item) {
void HistoryInner::addToSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item); const auto i = toItems->find(item);
if (i == toItems->cend()) { if (i == toItems->cend()) {
toItems->emplace(item, FullSelection); toItems->emplace(item, FullSelection);
} else if (i->second != FullSelection) { } else if (i->second != FullSelection) {
i->second = FullSelection; i->second = FullSelection;
} }
}; }
const auto removeSingle = [&](not_null<HistoryItem*> item) {
void HistoryInner::removeFromSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item); const auto i = toItems->find(item);
if (i != toItems->cend()) { if (i != toItems->cend()) {
toItems->erase(i); toItems->erase(i);
} }
};
const auto group = [&] {
if (const auto group = item->Get<HistoryMessageGroup>()) {
if (group->leader == item) {
return group;
} }
return group->leader->Get<HistoryMessageGroup>();
void HistoryInner::changeSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const {
if (action == SelectAction::Invert) {
action = isSelected(toItems, item)
? SelectAction::Deselect
: SelectAction::Select;
} }
return (HistoryMessageGroup*)nullptr; auto total = int(toItems->size());
}(); const auto add = (action == SelectAction::Select);
if (group) { if (add
&& goodForSelection(toItems, item, total)
&& total <= MaxSelectedItems) {
addToSelection(toItems, item);
} else {
removeFromSelection(toItems, item);
}
}
void HistoryInner::changeSelectionAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const {
const auto group = itemGroup(item);
if (!group) {
return changeSelection(toItems, item, action);
}
if (action == SelectAction::Invert) {
action = isSelectedAsGroup(toItems, item)
? SelectAction::Deselect
: SelectAction::Select;
}
auto total = int(toItems->size());
const auto add = (action == SelectAction::Select);
const auto adding = [&] { const auto adding = [&] {
if (!add || !goodForAdding(group->leader)) { if (!add || !goodForSelection(toItems, group->leader, total)) {
return false; return false;
} }
for (const auto other : group->others) { for (const auto other : group->others) {
if (!goodForAdding(other)) { if (!goodForSelection(toItems, other, total)) {
return false; return false;
} }
} }
return (total <= MaxSelectedItems); return (total <= MaxSelectedItems);
}(); }();
if (adding) { if (adding) {
addSingle(group->leader); addToSelection(toItems, group->leader);
for (const auto other : group->others) { for (const auto other : group->others) {
addSingle(other); addToSelection(toItems, other);
} }
} else { } else {
removeSingle(group->leader); removeFromSelection(toItems, group->leader);
for (const auto other : group->others) { for (const auto other : group->others) {
removeSingle(other); removeFromSelection(toItems, other);
} }
} }
}
void HistoryInner::forwardItem(not_null<HistoryItem*> item) {
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
void HistoryInner::forwardAsGroup(not_null<HistoryItem*> item) {
if (const auto group = itemGroup(item)) {
auto items = Auth().data().itemsToIds(group->others);
items.push_back(group->leader->fullId());
Window::ShowForwardMessagesBox(std::move(items));
} else { } else {
if (add && goodForAdding(item) && total <= MaxSelectedItems) { Window::ShowForwardMessagesBox({ 1, item->fullId() });
addSingle(item);
} else {
removeSingle(item);
} }
} }
void HistoryInner::deleteItem(not_null<HistoryItem*> item) {
if (auto message = item->toHistoryMessage()) {
if (message->uploading()) {
App::main()->cancelUploadLayer();
return;
}
}
const auto suggestModerateActions = true;
Ui::show(Box<DeleteMessagesBox>(item, suggestModerateActions));
}
void HistoryInner::deleteAsGroup(not_null<HistoryItem*> item) {
const auto group = itemGroup(item);
if (!group || group->others.empty()) {
return deleteItem(item);
}
auto items = Auth().data().itemsToIds(group->others);
items.push_back(group->leader->fullId());
Ui::show(Box<DeleteMessagesBox>(std::move(items)));
} }
void HistoryInner::addSelectionRange( void HistoryInner::addSelectionRange(
@ -2546,7 +2640,7 @@ void HistoryInner::addSelectionRange(
auto block = history->blocks[fromblock]; auto block = history->blocks[fromblock];
for (int cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) { for (int cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto item = block->items[fromitem]; auto item = block->items[fromitem];
changeDragSelection(toItems, item, SelectAction::Select); changeSelectionAsGroup(toItems, item, SelectAction::Select);
} }
if (toItems->size() >= MaxSelectedItems) break; if (toItems->size() >= MaxSelectedItems) break;
fromitem = 0; fromitem = 0;
@ -2601,7 +2695,7 @@ void HistoryInner::applyDragSelection(
} }
} }
for (const auto item : toRemove) { for (const auto item : toRemove) {
changeDragSelection(toItems, item, SelectAction::Deselect); changeSelectionAsGroup(toItems, item, SelectAction::Deselect);
} }
} }
} }

View File

@ -141,6 +141,12 @@ private:
Selecting, Selecting,
}; };
enum class SelectAction {
Select,
Deselect,
Invert,
};
void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button); void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
void mouseActionUpdate(const QPoint &screenPos); void mouseActionUpdate(const QPoint &screenPos);
void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button); void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button);
@ -220,6 +226,8 @@ private:
style::cursor _cursor = style::cur_default; style::cursor _cursor = style::cur_default;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>; using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
SelectedItems _selected; SelectedItems _selected;
HistoryMessageGroup *itemGroup(not_null<HistoryItem*> item) const;
void applyDragSelection(); void applyDragSelection();
void applyDragSelection(not_null<SelectedItems*> toItems) const; void applyDragSelection(not_null<SelectedItems*> toItems) const;
void addSelectionRange( void addSelectionRange(
@ -229,18 +237,34 @@ private:
int fromitem, int fromitem,
int toblock, int toblock,
int toitem) const; int toitem) const;
bool isFullSelected( bool isSelected(
not_null<SelectedItems*> toItems, not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const; not_null<HistoryItem*> item) const;
enum class SelectAction { bool isSelectedAsGroup(
Select, not_null<SelectedItems*> toItems,
Deselect, not_null<HistoryItem*> item) const;
Invert, bool goodForSelection(
}; not_null<SelectedItems*> toItems,
void changeDragSelection( not_null<HistoryItem*> item,
int &totalCount) const;
void addToSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
void removeFromSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
void changeSelection(
not_null<SelectedItems*> toItems, not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
SelectAction action) const; SelectAction action) const;
void changeSelectionAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const;
void forwardItem(not_null<HistoryItem*> item);
void forwardAsGroup(not_null<HistoryItem*> item);
void deleteItem(not_null<HistoryItem*> item);
void deleteAsGroup(not_null<HistoryItem*> item);
// Does any of the shown histories has this flag set. // Does any of the shown histories has this flag set.
bool hasPendingResizedItems() const { bool hasPendingResizedItems() const {

View File

@ -3735,20 +3735,6 @@ void HistoryWidget::onCmdStart() {
setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory); setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory);
} }
void HistoryWidget::forwardMessage() {
auto item = App::contextItem();
if (!item || item->id < 0 || item->serviceMsg()) return;
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
void HistoryWidget::selectMessage() {
auto item = App::contextItem();
if (!item || item->id < 0 || item->serviceMsg()) return;
if (_list) _list->selectItem(item);
}
void HistoryWidget::setMembersShowAreaActive(bool active) { void HistoryWidget::setMembersShowAreaActive(bool active) {
if (!active) { if (!active) {
_membersDropdownShowTimer.stop(); _membersDropdownShowTimer.stop();
@ -6198,19 +6184,6 @@ void HistoryWidget::onForwardSelected() {
}); });
} }
void HistoryWidget::confirmDeleteContextItem() {
auto item = App::contextItem();
if (!item) return;
if (auto message = item->toHistoryMessage()) {
if (message->uploading()) {
App::main()->cancelUploadLayer();
return;
}
}
App::main()->deleteLayer();
}
void HistoryWidget::confirmDeleteSelectedItems() { void HistoryWidget::confirmDeleteSelectedItems() {
if (!_list) return; if (!_list) return;
@ -6220,29 +6193,6 @@ void HistoryWidget::confirmDeleteSelectedItems() {
App::main()->deleteLayer(int(selected.size())); App::main()->deleteLayer(int(selected.size()));
} }
void HistoryWidget::deleteContextItem(bool forEveryone) {
Ui::hideLayer();
auto item = App::contextItem();
if (!item) {
return;
}
auto toDelete = QVector<MTPint>(1, MTP_int(item->id));
auto history = item->history();
auto wasOnServer = (item->id > 0);
auto wasLast = (history->lastMsg == item);
item->destroy();
if (!wasOnServer && wasLast && !history->lastMsg) {
App::main()->checkPeerHistory(history->peer);
}
if (wasOnServer) {
App::main()->deleteMessages(history->peer, toDelete, forEveryone);
}
}
void HistoryWidget::deleteSelectedItems(bool forEveryone) { void HistoryWidget::deleteSelectedItems(bool forEveryone) {
Ui::hideLayer(); Ui::hideLayer();
if (!_list) return; if (!_list) return;

View File

@ -325,9 +325,7 @@ public:
bool isItemVisible(HistoryItem *item); bool isItemVisible(HistoryItem *item);
void confirmDeleteContextItem();
void confirmDeleteSelectedItems(); void confirmDeleteSelectedItems();
void deleteContextItem(bool forEveryone);
void deleteSelectedItems(bool forEveryone); void deleteSelectedItems(bool forEveryone);
// Float player interface. // Float player interface.
@ -414,9 +412,6 @@ public slots:
void onWindowVisibleChanged(); void onWindowVisibleChanged();
void forwardMessage();
void selectMessage();
void onFieldFocused(); void onFieldFocused();
void onFieldResize(); void onFieldResize();
void onCheckFieldAutocomplete(); void onCheckFieldAutocomplete();

View File

@ -965,9 +965,18 @@ void MainWidget::cancelUploadLayer() {
return; return;
} }
Auth().uploader().pause(item->fullId()); const auto itemId = item->fullId();
Ui::show(Box<ConfirmBox>(lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), base::lambda_guarded(this, [this] { Auth().uploader().pause(itemId);
_history->deleteContextItem(false); Ui::show(Box<ConfirmBox>(lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), base::lambda_guarded(this, [=] {
Ui::hideLayer();
if (const auto item = App::histItemById(itemId)) {
const auto history = item->history();
const auto wasLast = (history->lastMsg == item);
item->destroy();
if (wasLast && !history->lastMsg) {
checkPeerHistory(history->peer);
}
}
Auth().uploader().unpause(); Auth().uploader().unpause();
}), base::lambda_guarded(this, [] { }), base::lambda_guarded(this, [] {
Auth().uploader().unpause(); Auth().uploader().unpause();