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 "ui/widgets/popup_menu.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/stickers.h"
#include "history/history_widget.h"
@ -1110,7 +1112,7 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu
&& !_pressWasInactive
&& !_selected.empty()
&& (_selected.cbegin()->second == FullSelection)) {
changeDragSelection(
changeSelectionAsGroup(
&_selected,
_mouseActionItem,
SelectAction::Invert);
@ -1220,7 +1222,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
isUponSelected = -1;
if (_selected.cbegin()->second == FullSelection) {
hasSelected = 2;
if (App::hoveredItem() && _selected.find(App::hoveredItem()) != _selected.cend()) {
if (_dragStateItem && _selected.find(_dragStateItem) != _selected.cend()) {
isUponSelected = 2;
} else {
isUponSelected = -2;
@ -1307,23 +1309,35 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}));
}
_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
} else if (_dragStateItem) {
if (isUponSelected != -2) {
if (App::hoveredLinkItem()->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true);
if (_dragStateItem->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
forwardItem(item);
}
}))->setEnabled(true);
}
if (App::hoveredLinkItem()->canDelete()) {
if (_dragStateItem->canDelete()) {
_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] {
// TODO
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelection(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
}
App::contextItem(App::hoveredLinkItem());
App::contextItem(_dragStateItem);
}
} else { // maybe cursor on some text history item?
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()));
} else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) {
const auto fullId = item->fullId();
if (isUponSelected != -2) {
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) {
_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()) {
_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 {
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();
}
}
@ -2428,112 +2465,169 @@ void HistoryInner::applyDragSelection() {
applyDragSelection(&_selected);
}
bool HistoryInner::isFullSelected(
HistoryMessageGroup *HistoryInner::itemGroup(
not_null<HistoryItem*> item) const {
if (const auto group = item->Get<HistoryMessageGroup>()) {
if (group->leader == item) {
return group;
}
return group->leader->Get<HistoryMessageGroup>();
}
return nullptr;
}
bool HistoryInner::isSelected(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto group = [&] {
if (const auto group = item->Get<HistoryMessageGroup>()) {
if (group->leader == item) {
return group;
}
return group->leader->Get<HistoryMessageGroup>();
}
return (HistoryMessageGroup*)nullptr;
}();
const auto singleSelected = [&](not_null<HistoryItem*> item) {
const auto i = toItems->find(item);
return (i != toItems->cend()) && (i->second == FullSelection);
};
if (group) {
if (!singleSelected(group->leader)) {
const auto i = toItems->find(item);
return (i != toItems->cend()) && (i->second == FullSelection);
}
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;
}
for (const auto other : group->others) {
if (!singleSelected(other)) {
if (!isSelected(toItems, other)) {
return false;
}
}
} else if (!singleSelected(item)) {
return true;
}
return isSelected(toItems, item);
}
bool HistoryInner::goodForSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
int &totalCount) const {
if (item->id <= 0 || item->serviceMsg()) {
return false;
}
if (toItems->find(item) == toItems->end()) {
++totalCount;
}
return true;
}
void HistoryInner::changeDragSelection(
void HistoryInner::addToSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
if (i == toItems->cend()) {
toItems->emplace(item, FullSelection);
} else if (i->second != FullSelection) {
i->second = FullSelection;
}
}
void HistoryInner::removeFromSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
if (i != toItems->cend()) {
toItems->erase(i);
}
}
void HistoryInner::changeSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const {
if (action == SelectAction::Invert) {
action = isFullSelected(toItems, item)
action = isSelected(toItems, item)
? SelectAction::Deselect
: SelectAction::Select;
}
auto total = toItems->size();
auto total = int(toItems->size());
const auto add = (action == SelectAction::Select);
const auto goodForAdding = [&](not_null<HistoryItem*> item) {
if (item->id <= 0 || item->serviceMsg()) {
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 = [&] {
if (!add || !goodForSelection(toItems, group->leader, total)) {
return false;
}
if (toItems->find(item) == toItems->end()) {
++total;
}
return true;
};
const auto addSingle = [&](not_null<HistoryItem*> item) {
const auto i = toItems->find(item);
if (i == toItems->cend()) {
toItems->emplace(item, FullSelection);
} else if (i->second != FullSelection) {
i->second = FullSelection;
}
};
const auto removeSingle = [&](not_null<HistoryItem*> item) {
const auto i = toItems->find(item);
if (i != toItems->cend()) {
toItems->erase(i);
}
};
const auto group = [&] {
if (const auto group = item->Get<HistoryMessageGroup>()) {
if (group->leader == item) {
return group;
}
return group->leader->Get<HistoryMessageGroup>();
}
return (HistoryMessageGroup*)nullptr;
}();
if (group) {
const auto adding = [&] {
if (!add || !goodForAdding(group->leader)) {
for (const auto other : group->others) {
if (!goodForSelection(toItems, other, total)) {
return false;
}
for (const auto other : group->others) {
if (!goodForAdding(other)) {
return false;
}
}
return (total <= MaxSelectedItems);
}();
if (adding) {
addSingle(group->leader);
for (const auto other : group->others) {
addSingle(other);
}
} else {
removeSingle(group->leader);
for (const auto other : group->others) {
removeSingle(other);
}
}
return (total <= MaxSelectedItems);
}();
if (adding) {
addToSelection(toItems, group->leader);
for (const auto other : group->others) {
addToSelection(toItems, other);
}
} else {
if (add && goodForAdding(item) && total <= MaxSelectedItems) {
addSingle(item);
} else {
removeSingle(item);
removeFromSelection(toItems, group->leader);
for (const auto other : group->others) {
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 {
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
}
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(
not_null<SelectedItems*> toItems,
not_null<History*> history,
@ -2546,7 +2640,7 @@ void HistoryInner::addSelectionRange(
auto block = history->blocks[fromblock];
for (int cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto item = block->items[fromitem];
changeDragSelection(toItems, item, SelectAction::Select);
changeSelectionAsGroup(toItems, item, SelectAction::Select);
}
if (toItems->size() >= MaxSelectedItems) break;
fromitem = 0;
@ -2601,7 +2695,7 @@ void HistoryInner::applyDragSelection(
}
}
for (const auto item : toRemove) {
changeDragSelection(toItems, item, SelectAction::Deselect);
changeSelectionAsGroup(toItems, item, SelectAction::Deselect);
}
}
}

View File

@ -141,6 +141,12 @@ private:
Selecting,
};
enum class SelectAction {
Select,
Deselect,
Invert,
};
void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
void mouseActionUpdate(const QPoint &screenPos);
void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button);
@ -220,6 +226,8 @@ private:
style::cursor _cursor = style::cur_default;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
SelectedItems _selected;
HistoryMessageGroup *itemGroup(not_null<HistoryItem*> item) const;
void applyDragSelection();
void applyDragSelection(not_null<SelectedItems*> toItems) const;
void addSelectionRange(
@ -229,18 +237,34 @@ private:
int fromitem,
int toblock,
int toitem) const;
bool isFullSelected(
bool isSelected(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
enum class SelectAction {
Select,
Deselect,
Invert,
};
void changeDragSelection(
bool isSelectedAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
bool goodForSelection(
not_null<SelectedItems*> toItems,
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<HistoryItem*> item,
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.
bool hasPendingResizedItems() const {

View File

@ -3735,20 +3735,6 @@ void HistoryWidget::onCmdStart() {
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) {
if (!active) {
_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() {
if (!_list) return;
@ -6220,29 +6193,6 @@ void HistoryWidget::confirmDeleteSelectedItems() {
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) {
Ui::hideLayer();
if (!_list) return;

View File

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

View File

@ -965,9 +965,18 @@ void MainWidget::cancelUploadLayer() {
return;
}
Auth().uploader().pause(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] {
_history->deleteContextItem(false);
const auto itemId = item->fullId();
Auth().uploader().pause(itemId);
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();
}), base::lambda_guarded(this, [] {
Auth().uploader().unpause();