mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 02:01:40 -05:00
Allow multiple items selection in HistoryView.
This commit is contained in:
parent
2aa477176c
commit
63c1212ef1
42 changed files with 1402 additions and 537 deletions
|
@ -574,20 +574,20 @@ void DeleteMessagesBox::deleteAndClear() {
|
|||
}
|
||||
}
|
||||
|
||||
if (!_singleItem) {
|
||||
App::main()->clearSelectedItems();
|
||||
if (_deleteConfirmedCallback) {
|
||||
_deleteConfirmedCallback();
|
||||
}
|
||||
|
||||
QMap<PeerData*, QVector<MTPint>> idsByPeer;
|
||||
for_const (auto fullId, _ids) {
|
||||
if (auto item = App::histItemById(fullId)) {
|
||||
for (const auto itemId : _ids) {
|
||||
if (auto item = App::histItemById(itemId)) {
|
||||
auto history = item->history();
|
||||
auto wasOnServer = (item->id > 0);
|
||||
auto wasLast = (history->lastMsg == item);
|
||||
item->destroy();
|
||||
|
||||
if (wasOnServer) {
|
||||
idsByPeer[history->peer].push_back(MTP_int(fullId.msg));
|
||||
idsByPeer[history->peer].push_back(MTP_int(itemId.msg));
|
||||
} else if (wasLast) {
|
||||
App::main()->checkPeerHistory(history->peer);
|
||||
}
|
||||
|
|
|
@ -166,6 +166,10 @@ public:
|
|||
bool suggestModerateActions);
|
||||
DeleteMessagesBox(QWidget*, MessageIdsList &&selected);
|
||||
|
||||
void setDeleteConfirmedCallback(base::lambda<void()> callback) {
|
||||
_deleteConfirmedCallback = std::move(callback);
|
||||
}
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
|
@ -188,6 +192,8 @@ private:
|
|||
object_ptr<Ui::Checkbox> _reportSpam = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _deleteAll = { nullptr };
|
||||
|
||||
base::lambda<void()> _deleteConfirmedCallback;
|
||||
|
||||
};
|
||||
|
||||
class ConfirmInviteBox : public BoxContent, public RPCSender {
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_item.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "data/data_session.h"
|
||||
|
@ -129,6 +130,19 @@ QString WithCaptionNotificationText(
|
|||
caption);
|
||||
}
|
||||
|
||||
TextWithEntities WithCaptionClipboardText(
|
||||
const QString &attachType,
|
||||
TextWithEntities &&caption) {
|
||||
TextWithEntities result;
|
||||
result.text.reserve(5 + attachType.size() + caption.text.size());
|
||||
result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
|
||||
if (!caption.text.isEmpty()) {
|
||||
result.text.append(qstr("\n"));
|
||||
TextUtilities::Append(result, std::move(caption));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Media::Media(not_null<HistoryItem*> parent) : _parent(parent) {
|
||||
|
@ -298,6 +312,12 @@ QString MediaPhoto::pinnedTextSubstring() const {
|
|||
return lang(lng_action_pinned_media_photo);
|
||||
}
|
||||
|
||||
TextWithEntities MediaPhoto::clipboardText() const {
|
||||
return WithCaptionClipboardText(
|
||||
lang(lng_in_dlg_photo),
|
||||
parent()->clipboardText());
|
||||
}
|
||||
|
||||
bool MediaPhoto::allowsEditCaption() const {
|
||||
return true;
|
||||
}
|
||||
|
@ -536,6 +556,36 @@ QString MediaFile::pinnedTextSubstring() const {
|
|||
return lang(lng_action_pinned_media_file);
|
||||
}
|
||||
|
||||
TextWithEntities MediaFile::clipboardText() const {
|
||||
const auto attachType = [&] {
|
||||
const auto name = _document->composeNameString();
|
||||
const auto addName = !name.isEmpty()
|
||||
? qstr(" : ") + name
|
||||
: QString();
|
||||
if (const auto sticker = _document->sticker()) {
|
||||
if (!_emoji.isEmpty()) {
|
||||
return lng_in_dlg_sticker_emoji(lt_emoji, _emoji);
|
||||
}
|
||||
return lang(lng_in_dlg_sticker);
|
||||
} else if (_document->isAnimation()) {
|
||||
if (_document->isVideoMessage()) {
|
||||
return lang(lng_in_dlg_video_message);
|
||||
}
|
||||
return qsl("GIF");
|
||||
} else if (_document->isVideoFile()) {
|
||||
return lang(lng_in_dlg_video);
|
||||
} else if (_document->isVoiceMessage()) {
|
||||
return lang(lng_in_dlg_audio) + addName;
|
||||
} else if (_document->isSong()) {
|
||||
return lang(lng_in_dlg_audio_file) + addName;
|
||||
}
|
||||
return lang(lng_in_dlg_file) + addName;
|
||||
}();
|
||||
return WithCaptionClipboardText(
|
||||
attachType,
|
||||
parent()->clipboardText());
|
||||
}
|
||||
|
||||
bool MediaFile::allowsEditCaption() const {
|
||||
return !_document->isVideoMessage() && !_document->sticker();
|
||||
}
|
||||
|
@ -666,6 +716,18 @@ QString MediaContact::pinnedTextSubstring() const {
|
|||
return lang(lng_action_pinned_media_contact);
|
||||
}
|
||||
|
||||
TextWithEntities MediaContact::clipboardText() const {
|
||||
const auto text = qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n")
|
||||
+ lng_full_name(
|
||||
lt_first_name,
|
||||
_contact.firstName,
|
||||
lt_last_name,
|
||||
_contact.lastName).trimmed()
|
||||
+ '\n'
|
||||
+ _contact.phoneNumber;
|
||||
return { text, EntitiesInText() };
|
||||
}
|
||||
|
||||
bool MediaContact::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
return false;
|
||||
}
|
||||
|
@ -734,6 +796,29 @@ QString MediaLocation::pinnedTextSubstring() const {
|
|||
return lang(lng_action_pinned_media_location);
|
||||
}
|
||||
|
||||
TextWithEntities MediaLocation::clipboardText() const {
|
||||
TextWithEntities result = {
|
||||
qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"),
|
||||
EntitiesInText()
|
||||
};
|
||||
auto titleResult = TextUtilities::ParseEntities(
|
||||
TextUtilities::Clean(_title),
|
||||
Ui::WebpageTextTitleOptions().flags);
|
||||
auto descriptionResult = TextUtilities::ParseEntities(
|
||||
TextUtilities::Clean(_description),
|
||||
TextParseLinks | TextParseMultiline | TextParseRichText);
|
||||
if (!titleResult.text.isEmpty()) {
|
||||
TextUtilities::Append(result, std::move(titleResult));
|
||||
result.text.append('\n');
|
||||
}
|
||||
if (!descriptionResult.text.isEmpty()) {
|
||||
TextUtilities::Append(result, std::move(descriptionResult));
|
||||
result.text.append('\n');
|
||||
}
|
||||
result.text += LocationClickHandler(_location->coords).dragText();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MediaLocation::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
return false;
|
||||
}
|
||||
|
@ -783,6 +868,10 @@ QString MediaCall::pinnedTextSubstring() const {
|
|||
return QString();
|
||||
}
|
||||
|
||||
TextWithEntities MediaCall::clipboardText() const {
|
||||
return { qsl("[ ") + notificationText() + qsl(" ]"), EntitiesInText() };
|
||||
}
|
||||
|
||||
bool MediaCall::allowsForward() const {
|
||||
return false;
|
||||
}
|
||||
|
@ -852,6 +941,10 @@ QString MediaWebPage::pinnedTextSubstring() const {
|
|||
return QString();
|
||||
}
|
||||
|
||||
TextWithEntities MediaWebPage::clipboardText() const {
|
||||
return TextWithEntities();
|
||||
}
|
||||
|
||||
bool MediaWebPage::allowsEdit() const {
|
||||
return true;
|
||||
}
|
||||
|
@ -904,6 +997,10 @@ QString MediaGame::pinnedTextSubstring() const {
|
|||
return lng_action_pinned_media_game(lt_game, title);
|
||||
}
|
||||
|
||||
TextWithEntities MediaGame::clipboardText() const {
|
||||
return TextWithEntities();
|
||||
}
|
||||
|
||||
QString MediaGame::errorTextForForward(
|
||||
not_null<ChannelData*> channel) const {
|
||||
if (channel->restricted(ChannelRestriction::f_send_games)) {
|
||||
|
@ -965,6 +1062,10 @@ QString MediaInvoice::pinnedTextSubstring() const {
|
|||
return QString();
|
||||
}
|
||||
|
||||
TextWithEntities MediaInvoice::clipboardText() const {
|
||||
return TextWithEntities();
|
||||
}
|
||||
|
||||
bool MediaInvoice::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ public:
|
|||
virtual QString chatsListText() const;
|
||||
virtual QString notificationText() const = 0;
|
||||
virtual QString pinnedTextSubstring() const = 0;
|
||||
virtual TextWithEntities clipboardText() const = 0;
|
||||
virtual bool allowsForward() const;
|
||||
virtual bool allowsEdit() const;
|
||||
virtual bool allowsEditCaption() const;
|
||||
|
@ -136,6 +137,7 @@ public:
|
|||
QString chatsListText() const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
bool allowsEditCaption() const override;
|
||||
QString errorTextForForward(
|
||||
not_null<ChannelData*> channel) const override;
|
||||
|
@ -169,6 +171,7 @@ public:
|
|||
QString chatsListText() const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
bool allowsEditCaption() const override;
|
||||
bool forwardedBecomesUnread() const override;
|
||||
QString errorTextForForward(
|
||||
|
@ -201,6 +204,7 @@ public:
|
|||
const SharedContact *sharedContact() const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
|
||||
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
bool updateSentMedia(const MTPMessageMedia &media) override;
|
||||
|
@ -230,6 +234,7 @@ public:
|
|||
QString chatsListText() const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
|
||||
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
bool updateSentMedia(const MTPMessageMedia &media) override;
|
||||
|
@ -255,6 +260,7 @@ public:
|
|||
const Call *call() const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
bool allowsForward() const override;
|
||||
bool allowsRevoke() const override;
|
||||
|
||||
|
@ -286,6 +292,7 @@ public:
|
|||
QString chatsListText() const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
bool allowsEdit() const override;
|
||||
|
||||
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
|
@ -311,6 +318,7 @@ public:
|
|||
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
QString errorTextForForward(
|
||||
not_null<ChannelData*> channel) const override;
|
||||
|
||||
|
@ -343,6 +351,7 @@ public:
|
|||
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
|
||||
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
bool updateSentMedia(const MTPMessageMedia &media) override;
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_media_types.h"
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item_text.h"
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "history/admin_log/history_admin_log_filter.h"
|
||||
#include "history/view/history_view_message.h"
|
||||
|
@ -486,6 +487,11 @@ std::unique_ptr<HistoryView::Element> InnerWidget::elementCreate(
|
|||
return std::make_unique<HistoryView::Service>(this, message);
|
||||
}
|
||||
|
||||
bool InnerWidget::elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) {
|
||||
return (App::hoveredItem() == view);
|
||||
}
|
||||
|
||||
void InnerWidget::elementAnimationAutoplayAsync(
|
||||
not_null<const HistoryView::Element*> view) {
|
||||
crl::on_main(this, [this, msgId = view->data()->fullId()] {
|
||||
|
@ -1121,9 +1127,7 @@ void InnerWidget::openContextGif(FullMsgId itemId) {
|
|||
|
||||
void InnerWidget::copyContextText(FullMsgId itemId) {
|
||||
if (const auto item = App::histItemById(itemId)) {
|
||||
if (const auto view = viewForItem(item)) {
|
||||
SetClipboardWithEntities(view->selectedText(FullSelection));
|
||||
}
|
||||
SetClipboardWithEntities(HistoryItemText(item));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,8 +74,10 @@ public:
|
|||
not_null<HistoryMessage*> message) override;
|
||||
std::unique_ptr<HistoryView::Element> elementCreate(
|
||||
not_null<HistoryService*> message) override;
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
void elementAnimationAutoplayAsync(
|
||||
not_null<const HistoryView::Element*> element) override;
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
|
||||
~InnerWidget();
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_message.h"
|
||||
#include "history/view/history_view_service_message.h"
|
||||
#include "history/history_item.h"
|
||||
#include "mainwidget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
|
@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/widgets/popup_menu.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "data/data_feed_messages.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_document.h"
|
||||
|
@ -69,6 +71,18 @@ Widget::Widget(
|
|||
_topBar->move(0, 0);
|
||||
_topBar->resizeToWidth(width());
|
||||
_topBar->show();
|
||||
_topBar->forwardSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
forwardSelected();
|
||||
}, _topBar->lifetime());
|
||||
_topBar->deleteSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
confirmDeleteSelected();
|
||||
}, _topBar->lifetime());
|
||||
_topBar->clearSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
clearSelected();
|
||||
}, _topBar->lifetime());
|
||||
|
||||
_topBarShadow->raise();
|
||||
updateAdaptiveLayout();
|
||||
|
@ -175,6 +189,30 @@ rpl::producer<Data::MessagesSlice> Widget::listSource(
|
|||
limitAfter);
|
||||
}
|
||||
|
||||
bool Widget::listAllowsMultiSelect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Widget::listIsLessInOrder(
|
||||
not_null<HistoryItem*> first,
|
||||
not_null<HistoryItem*> second) {
|
||||
return first->position() < second->position();
|
||||
}
|
||||
|
||||
void Widget::listSelectionChanged(HistoryView::SelectedItems &&items) {
|
||||
HistoryView::TopBarWidget::SelectedState state;
|
||||
state.count = items.size();
|
||||
for (const auto item : items) {
|
||||
if (item.canForward) {
|
||||
++state.canForwardCount;
|
||||
}
|
||||
if (item.canDelete) {
|
||||
++state.canDeleteCount;
|
||||
}
|
||||
}
|
||||
_topBar->showSelected(state);
|
||||
}
|
||||
|
||||
std::unique_ptr<Window::SectionMemento> Widget::createMemento() {
|
||||
auto result = std::make_unique<Memento>(_feed);
|
||||
saveState(result.get());
|
||||
|
@ -280,4 +318,35 @@ QRect Widget::rectForFloatPlayer() const {
|
|||
return mapToGlobal(_scroll->geometry());
|
||||
}
|
||||
|
||||
void Widget::forwardSelected() {
|
||||
auto items = _inner->getSelectedItems();
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto weak = make_weak(this);
|
||||
Window::ShowForwardMessagesBox(std::move(items), [=] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->clearSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Widget::confirmDeleteSelected() {
|
||||
auto items = _inner->getSelectedItems();
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto weak = make_weak(this);
|
||||
const auto box = Ui::show(Box<DeleteMessagesBox>(std::move(items)));
|
||||
box->setDeleteConfirmedCallback([=] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->clearSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Widget::clearSelected() {
|
||||
_inner->cancelSelection();
|
||||
}
|
||||
|
||||
} // namespace HistoryFeed
|
||||
|
|
|
@ -68,6 +68,12 @@ public:
|
|||
Data::MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) override;
|
||||
bool listAllowsMultiSelect() override;
|
||||
bool listIsLessInOrder(
|
||||
not_null<HistoryItem*> first,
|
||||
not_null<HistoryItem*> second) override;
|
||||
void listSelectionChanged(
|
||||
HistoryView::SelectedItems &&items) override;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
@ -86,6 +92,10 @@ private:
|
|||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
||||
void forwardSelected();
|
||||
void confirmDeleteSelected();
|
||||
void clearSelected();
|
||||
|
||||
not_null<Data::Feed*> _feed;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
QPointer<HistoryView::ListWidget> _inner;
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_message.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item_text.h"
|
||||
#include "history/view/history_view_message.h"
|
||||
#include "history/view/history_view_service_message.h"
|
||||
#include "ui/text_options.h"
|
||||
|
@ -1326,7 +1327,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
|
||||
// -2 - has full selected items, but not over, -1 - has selection, but no over, 0 - no selection, 1 - over text, 2 - over full selected items
|
||||
auto isUponSelected = 0;
|
||||
auto hasSelected = 0;;
|
||||
auto hasSelected = 0;
|
||||
if (!_selected.empty()) {
|
||||
isUponSelected = -1;
|
||||
if (_selected.cbegin()->second == FullSelection) {
|
||||
|
@ -1441,14 +1442,18 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
}
|
||||
if (isUponSelected > 1) {
|
||||
if (selectedState.count > 0 && selectedState.canForwardCount == selectedState.count) {
|
||||
_menu->addAction(lang(lng_context_forward_selected), _widget, SLOT(onForwardSelected()));
|
||||
_menu->addAction(lang(lng_context_forward_selected), [=] {
|
||||
_widget->forwardSelected();
|
||||
});
|
||||
}
|
||||
if (selectedState.count > 0 && selectedState.canDeleteCount == selectedState.count) {
|
||||
_menu->addAction(lang(lng_context_delete_selected), [=] {
|
||||
_widget->confirmDeleteSelectedItems();
|
||||
_widget->confirmDeleteSelected();
|
||||
});
|
||||
}
|
||||
_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
|
||||
_menu->addAction(lang(lng_context_clear_selection), [=] {
|
||||
_widget->clearSelected();
|
||||
});
|
||||
} else if (item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (isUponSelected != -2) {
|
||||
|
@ -1576,14 +1581,18 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
}
|
||||
if (isUponSelected > 1) {
|
||||
if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) {
|
||||
_menu->addAction(lang(lng_context_forward_selected), _widget, SLOT(onForwardSelected()));
|
||||
_menu->addAction(lang(lng_context_forward_selected), [=] {
|
||||
_widget->forwardSelected();
|
||||
});
|
||||
}
|
||||
if (selectedState.count > 0 && selectedState.count == selectedState.canDeleteCount) {
|
||||
_menu->addAction(lang(lng_context_delete_selected), [=] {
|
||||
_widget->confirmDeleteSelectedItems();
|
||||
_widget->confirmDeleteSelected();
|
||||
});
|
||||
}
|
||||
_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
|
||||
_menu->addAction(lang(lng_context_clear_selection), [=] {
|
||||
_widget->clearSelected();
|
||||
});
|
||||
} else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) {
|
||||
if (isUponSelected != -2) {
|
||||
if (canForward) {
|
||||
|
@ -1709,10 +1718,8 @@ void HistoryInner::saveContextGif(FullMsgId itemId) {
|
|||
|
||||
void HistoryInner::copyContextText(FullMsgId itemId) {
|
||||
if (const auto item = App::histItemById(itemId)) {
|
||||
if (const auto view = item->mainView()) {
|
||||
// #TODO check for a group
|
||||
SetClipboardWithEntities(view->selectedText(FullSelection));
|
||||
}
|
||||
// #TODO check for a group
|
||||
SetClipboardWithEntities(HistoryItemText(item));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1743,13 +1750,11 @@ TextWithEntities HistoryInner::getSelectedText() const {
|
|||
auto fullSize = 0;
|
||||
auto texts = base::flat_map<std::pair<int, MsgId>, TextWithEntities>();
|
||||
|
||||
const auto addItem = [&](
|
||||
not_null<HistoryView::Element*> view,
|
||||
TextSelection selection) {
|
||||
const auto addItem = [&](not_null<HistoryView::Element*> view) {
|
||||
const auto item = view->data();
|
||||
auto time = item->date.toString(timeFormat);
|
||||
auto part = TextWithEntities();
|
||||
auto unwrapped = view->selectedText(selection);
|
||||
auto unwrapped = HistoryItemText(item);
|
||||
auto size = item->author()->name.size()
|
||||
+ time.size()
|
||||
+ unwrapped.text.size();
|
||||
|
@ -1780,17 +1785,17 @@ TextWithEntities HistoryInner::getSelectedText() const {
|
|||
// group->leader);
|
||||
//if (leaderSelection == FullSelection) {
|
||||
// groupLeadersAdded.emplace(group->leader);
|
||||
// addItem(group->leader, FullSelection);
|
||||
// addItem(group->leader);
|
||||
//} else if (view == group->leader) {
|
||||
// const auto leaderFullSelection = AddGroupItemSelection(
|
||||
// TextSelection(),
|
||||
// int(group->others.size()));
|
||||
// addItem(view, leaderFullSelection);
|
||||
//} else {
|
||||
// addItem(view, FullSelection);
|
||||
// addItem(view);
|
||||
//}
|
||||
} else {
|
||||
addItem(view, FullSelection);
|
||||
addItem(view);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1820,7 +1825,7 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) {
|
|||
auto selectedState = getSelectionState();
|
||||
if (selectedState.count > 0
|
||||
&& selectedState.canDeleteCount == selectedState.count) {
|
||||
_widget->confirmDeleteSelectedItems();
|
||||
_widget->confirmDeleteSelected();
|
||||
}
|
||||
} else {
|
||||
e->ignore();
|
||||
|
@ -2106,7 +2111,7 @@ bool HistoryInner::focusNextPrevChild(bool next) {
|
|||
if (_selected.empty()) {
|
||||
return TWidget::focusNextPrevChild(next);
|
||||
} else {
|
||||
clearSelectedItems();
|
||||
clearSelected();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2208,7 +2213,7 @@ auto HistoryInner::getSelectionState() const
|
|||
return result;
|
||||
}
|
||||
|
||||
void HistoryInner::clearSelectedItems(bool onlyTextSelection) {
|
||||
void HistoryInner::clearSelected(bool onlyTextSelection) {
|
||||
if (!_selected.empty() && (!onlyTextSelection || _selected.cbegin()->second != FullSelection)) {
|
||||
_selected.clear();
|
||||
_widget->updateTopBarSelection();
|
||||
|
@ -2927,6 +2932,10 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
|
|||
not_null<HistoryService*> message) override {
|
||||
return std::make_unique<HistoryView::Service>(this, message);
|
||||
}
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override {
|
||||
return (App::hoveredItem() == view);
|
||||
}
|
||||
void elementAnimationAutoplayAsync(
|
||||
not_null<const HistoryView::Element*> view) override {
|
||||
crl::on_main(&Auth(), [msgId = view->data()->fullId()] {
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
bool canDeleteSelected() const;
|
||||
|
||||
HistoryView::TopBarWidget::SelectedState getSelectionState() const;
|
||||
void clearSelectedItems(bool onlyTextSelection = false);
|
||||
void clearSelected(bool onlyTextSelection = false);
|
||||
MessageIdsList getSelectedItems() const;
|
||||
void selectItem(not_null<HistoryItem*> item);
|
||||
|
||||
|
|
|
@ -679,14 +679,25 @@ HistoryItem::~HistoryItem() {
|
|||
}
|
||||
}
|
||||
|
||||
ClickHandlerPtr goToMessageClickHandler(
|
||||
not_null<HistoryItem*> item,
|
||||
FullMsgId returnToId) {
|
||||
return goToMessageClickHandler(
|
||||
item->history()->peer,
|
||||
item->id,
|
||||
returnToId);
|
||||
}
|
||||
|
||||
ClickHandlerPtr goToMessageClickHandler(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId) {
|
||||
MsgId msgId,
|
||||
FullMsgId returnToId) {
|
||||
return std::make_shared<LambdaClickHandler>([=] {
|
||||
if (App::main()) {
|
||||
auto view = App::mousedItem();
|
||||
if (view && view->data()->history()->peer == peer) {
|
||||
App::main()->pushReplyReturn(view->data());
|
||||
if (const auto main = App::main()) {
|
||||
if (const auto returnTo = App::histItemById(returnToId)) {
|
||||
if (returnTo->history()->peer == peer) {
|
||||
main->pushReplyReturn(returnTo);
|
||||
}
|
||||
}
|
||||
App::wnd()->controller()->showPeerHistory(
|
||||
peer,
|
||||
|
@ -696,10 +707,6 @@ ClickHandlerPtr goToMessageClickHandler(
|
|||
});
|
||||
}
|
||||
|
||||
ClickHandlerPtr goToMessageClickHandler(not_null<HistoryItem*> item) {
|
||||
return goToMessageClickHandler(item->history()->peer, item->id);
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> HistoryItem::Create(
|
||||
not_null<History*> history,
|
||||
const MTPMessage &message) {
|
||||
|
|
|
@ -180,6 +180,9 @@ public:
|
|||
virtual TextWithEntities originalText() const {
|
||||
return { QString(), EntitiesInText() };
|
||||
}
|
||||
virtual TextWithEntities clipboardText() const {
|
||||
return { QString(), EntitiesInText() };
|
||||
}
|
||||
|
||||
virtual void setViewsCount(int32 count) {
|
||||
}
|
||||
|
@ -302,5 +305,8 @@ private:
|
|||
|
||||
ClickHandlerPtr goToMessageClickHandler(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId);
|
||||
ClickHandlerPtr goToMessageClickHandler(not_null<HistoryItem*> item);
|
||||
MsgId msgId,
|
||||
FullMsgId returnToId = FullMsgId());
|
||||
ClickHandlerPtr goToMessageClickHandler(
|
||||
not_null<HistoryItem*> item,
|
||||
FullMsgId returnToId = FullMsgId());
|
||||
|
|
|
@ -125,7 +125,9 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
|
|||
}
|
||||
}
|
||||
|
||||
bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) {
|
||||
bool HistoryMessageReply::updateData(
|
||||
not_null<HistoryMessage*> holder,
|
||||
bool force) {
|
||||
if (!force) {
|
||||
if (replyToMsg || !replyToMsgId) {
|
||||
return true;
|
||||
|
@ -152,7 +154,7 @@ bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) {
|
|||
|
||||
updateName();
|
||||
|
||||
replyToLnk = goToMessageClickHandler(replyToMsg);
|
||||
replyToLnk = goToMessageClickHandler(replyToMsg, holder->fullId());
|
||||
if (!replyToMsg->Has<HistoryMessageForwarded>()) {
|
||||
if (auto bot = replyToMsg->viaBot()) {
|
||||
replyToVia = std::make_unique<HistoryMessageVia>();
|
||||
|
@ -168,7 +170,7 @@ bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) {
|
|||
return (replyToMsg || !replyToMsgId);
|
||||
}
|
||||
|
||||
void HistoryMessageReply::clearData(HistoryMessage *holder) {
|
||||
void HistoryMessageReply::clearData(not_null<HistoryMessage*> holder) {
|
||||
replyToVia = nullptr;
|
||||
if (replyToMsg) {
|
||||
App::historyUnregDependency(holder, replyToMsg);
|
||||
|
|
|
@ -84,10 +84,10 @@ struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, Histor
|
|||
Expects(replyToVia == nullptr);
|
||||
}
|
||||
|
||||
bool updateData(HistoryMessage *holder, bool force = false);
|
||||
bool updateData(not_null<HistoryMessage*> holder, bool force = false);
|
||||
|
||||
// Must be called before destructor.
|
||||
void clearData(HistoryMessage *holder);
|
||||
void clearData(not_null<HistoryMessage*> holder);
|
||||
|
||||
bool isNameUpdated() const;
|
||||
void updateName() const;
|
||||
|
|
106
Telegram/SourceFiles/history/history_item_text.cpp
Normal file
106
Telegram/SourceFiles/history/history_item_text.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/history_item_text.h"
|
||||
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/text_options.h"
|
||||
|
||||
TextWithEntities WrapAsReply(
|
||||
TextWithEntities &&text,
|
||||
not_null<HistoryItem*> to) {
|
||||
const auto name = to->author()->name;
|
||||
auto result = TextWithEntities();
|
||||
result.text.reserve(
|
||||
lang(lng_in_reply_to).size()
|
||||
+ name.size()
|
||||
+ 4
|
||||
+ text.text.size());
|
||||
result.text.append('['
|
||||
).append(lang(lng_in_reply_to)
|
||||
).append(' '
|
||||
).append(name
|
||||
).append(qsl("]\n")
|
||||
);
|
||||
TextUtilities::Append(result, std::move(text));
|
||||
return result;
|
||||
}
|
||||
|
||||
TextWithEntities WrapAsForwarded(
|
||||
TextWithEntities &&text,
|
||||
not_null<HistoryMessageForwarded*> forwarded) {
|
||||
auto info = forwarded->text.originalTextWithEntities(
|
||||
AllTextSelection,
|
||||
ExpandLinksAll);
|
||||
auto result = TextWithEntities();
|
||||
result.text.reserve(
|
||||
info.text.size()
|
||||
+ 4
|
||||
+ text.text.size());
|
||||
result.entities.reserve(
|
||||
info.entities.size()
|
||||
+ text.entities.size());
|
||||
result.text.append('[');
|
||||
TextUtilities::Append(result, std::move(info));
|
||||
result.text.append(qsl("]\n"));
|
||||
TextUtilities::Append(result, std::move(text));
|
||||
return result;
|
||||
}
|
||||
|
||||
TextWithEntities HistoryItemText(not_null<HistoryItem*> item) {
|
||||
const auto media = item->media();
|
||||
|
||||
auto textResult = item->clipboardText();
|
||||
auto mediaResult = media ? media->clipboardText() : TextWithEntities();
|
||||
auto logEntryOriginalResult = [&] {
|
||||
const auto entry = item->Get<HistoryMessageLogEntryOriginal>();
|
||||
if (!entry) {
|
||||
return TextWithEntities();
|
||||
}
|
||||
const auto title = TextUtilities::SingleLine(entry->page->title.isEmpty()
|
||||
? entry->page->author
|
||||
: entry->page->title);
|
||||
auto titleResult = TextUtilities::ParseEntities(
|
||||
title,
|
||||
Ui::WebpageTextTitleOptions().flags);
|
||||
auto descriptionResult = entry->page->description;
|
||||
if (titleResult.text.isEmpty()) {
|
||||
return descriptionResult;
|
||||
} else if (descriptionResult.text.isEmpty()) {
|
||||
return titleResult;
|
||||
}
|
||||
titleResult.text += '\n';
|
||||
TextUtilities::Append(titleResult, std::move(descriptionResult));
|
||||
return titleResult;
|
||||
}();
|
||||
auto result = textResult;
|
||||
if (result.text.isEmpty()) {
|
||||
result = std::move(mediaResult);
|
||||
} else if (!mediaResult.text.isEmpty()) {
|
||||
result.text += qstr("\n\n");
|
||||
TextUtilities::Append(result, std::move(mediaResult));
|
||||
}
|
||||
if (result.text.isEmpty()) {
|
||||
result = std::move(logEntryOriginalResult);
|
||||
} else if (!logEntryOriginalResult.text.isEmpty()) {
|
||||
result.text += qstr("\n\n");
|
||||
TextUtilities::Append(result, std::move(logEntryOriginalResult));
|
||||
}
|
||||
if (const auto reply = item->Get<HistoryMessageReply>()) {
|
||||
if (const auto message = reply->replyToMsg) {
|
||||
result = WrapAsReply(std::move(result), message);
|
||||
}
|
||||
}
|
||||
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||
result = WrapAsForwarded(std::move(result), forwarded);
|
||||
}
|
||||
return result;
|
||||
}
|
10
Telegram/SourceFiles/history/history_item_text.h
Normal file
10
Telegram/SourceFiles/history/history_item_text.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
TextWithEntities HistoryItemText(not_null<HistoryItem*> item);
|
|
@ -59,7 +59,9 @@ public:
|
|||
|
||||
virtual HistoryMediaType type() const = 0;
|
||||
|
||||
virtual TextWithEntities selectedText(TextSelection selection) const = 0;
|
||||
virtual TextWithEntities selectedText(TextSelection selection) const {
|
||||
return TextWithEntities();
|
||||
}
|
||||
|
||||
bool hasPoint(QPoint point) const {
|
||||
return QRect(0, 0, width(), height()).contains(point);
|
||||
|
|
|
@ -261,15 +261,17 @@ TextSelection HistoryGroupedMedia::adjustSelection(
|
|||
|
||||
TextWithEntities HistoryGroupedMedia::selectedText(
|
||||
TextSelection selection) const {
|
||||
if (!IsSubGroupSelection(selection)) {
|
||||
return WithCaptionSelectedText(
|
||||
lang(lng_in_dlg_album),
|
||||
_caption,
|
||||
selection);
|
||||
} else if (IsGroupItemSelection(selection, int(_parts.size()) - 1)) {
|
||||
return main()->selectedText(FullSelection);
|
||||
}
|
||||
return TextWithEntities();
|
||||
return _caption.originalTextWithEntities(selection, ExpandLinksAll);
|
||||
// #TODO group select
|
||||
//if (!IsSubGroupSelection(selection)) {
|
||||
// return WithCaptionSelectedText(
|
||||
// lang(lng_in_dlg_album),
|
||||
// _caption,
|
||||
// selection);
|
||||
//} else if (IsGroupItemSelection(selection, int(_parts.size()) - 1)) {
|
||||
// return main()->selectedText(FullSelection);
|
||||
//}
|
||||
//return TextWithEntities();
|
||||
}
|
||||
|
||||
void HistoryGroupedMedia::clickHandlerActiveChanged(
|
||||
|
|
|
@ -94,27 +94,6 @@ std::unique_ptr<HistoryMedia> CreateAttach(
|
|||
|
||||
} // namespace
|
||||
|
||||
TextWithEntities WithCaptionSelectedText(
|
||||
const QString &attachType,
|
||||
const Text &caption,
|
||||
TextSelection selection) {
|
||||
if (selection != FullSelection) {
|
||||
return caption.originalTextWithEntities(selection, ExpandLinksAll);
|
||||
}
|
||||
|
||||
TextWithEntities result, original;
|
||||
if (!caption.isEmpty()) {
|
||||
original = caption.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
|
||||
}
|
||||
result.text.reserve(5 + attachType.size() + original.text.size());
|
||||
result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
|
||||
if (!caption.isEmpty()) {
|
||||
result.text.append(qstr("\n"));
|
||||
TextUtilities::Append(result, std::move(original));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||
if (p == _savel || p == _cancell) {
|
||||
if (active && !dataLoaded()) {
|
||||
|
@ -682,10 +661,7 @@ void HistoryPhoto::validateGroupedCache(
|
|||
}
|
||||
|
||||
TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const {
|
||||
return WithCaptionSelectedText(
|
||||
lang(lng_in_dlg_photo),
|
||||
_caption,
|
||||
selection);
|
||||
return _caption.originalTextWithEntities(selection, ExpandLinksAll);
|
||||
}
|
||||
|
||||
bool HistoryPhoto::needsBubble() const {
|
||||
|
@ -1122,10 +1098,7 @@ void HistoryVideo::setStatusSize(int newSize) const {
|
|||
}
|
||||
|
||||
TextWithEntities HistoryVideo::selectedText(TextSelection selection) const {
|
||||
return WithCaptionSelectedText(
|
||||
lang(lng_in_dlg_video),
|
||||
_caption,
|
||||
selection);
|
||||
return _caption.originalTextWithEntities(selection, ExpandLinksAll);
|
||||
}
|
||||
|
||||
bool HistoryVideo::needsBubble() const {
|
||||
|
@ -1715,38 +1688,11 @@ bool HistoryDocument::hasTextForCopy() const {
|
|||
}
|
||||
|
||||
TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
|
||||
TextWithEntities result;
|
||||
buildStringRepresentation([&result, selection](const QString &type, const QString &fileName, const Text &caption) {
|
||||
auto fullType = type;
|
||||
if (!fileName.isEmpty()) {
|
||||
fullType.append(qstr(" : ")).append(fileName);
|
||||
}
|
||||
result = WithCaptionSelectedText(fullType, caption, selection);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void HistoryDocument::buildStringRepresentation(Callback callback) const {
|
||||
const Text emptyCaption;
|
||||
const Text *caption = &emptyCaption;
|
||||
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||
caption = &captioned->_caption;
|
||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||
const auto &caption = captioned->_caption;
|
||||
return caption.originalTextWithEntities(selection, ExpandLinksAll);
|
||||
}
|
||||
QString attachType = lang(lng_in_dlg_file);
|
||||
if (Has<HistoryDocumentVoice>()) {
|
||||
attachType = lang(lng_in_dlg_audio);
|
||||
} else if (_data->isAudioFile()) {
|
||||
attachType = lang(lng_in_dlg_audio_file);
|
||||
}
|
||||
|
||||
QString attachFileName;
|
||||
if (auto named = Get<HistoryDocumentNamed>()) {
|
||||
if (!named->_name.isEmpty()) {
|
||||
attachFileName = named->_name;
|
||||
}
|
||||
}
|
||||
return callback(attachType, attachFileName, *caption);
|
||||
return TextWithEntities();
|
||||
}
|
||||
|
||||
void HistoryDocument::setStatusSize(int newSize, qint64 realDuration) const {
|
||||
|
@ -2521,7 +2467,7 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request)
|
|||
}
|
||||
|
||||
TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
|
||||
return WithCaptionSelectedText(mediaTypeString(), _caption, selection);
|
||||
return _caption.originalTextWithEntities(selection, ExpandLinksAll);
|
||||
}
|
||||
|
||||
bool HistoryGif::needsBubble() const {
|
||||
|
@ -2979,17 +2925,6 @@ HistoryTextState HistorySticker::getState(QPoint point, HistoryStateRequest requ
|
|||
return result;
|
||||
}
|
||||
|
||||
QString HistorySticker::toString() const {
|
||||
return _emoji.isEmpty() ? lang(lng_in_dlg_sticker) : lng_in_dlg_sticker_emoji(lt_emoji, _emoji);
|
||||
}
|
||||
|
||||
TextWithEntities HistorySticker::selectedText(TextSelection selection) const {
|
||||
if (selection != FullSelection) {
|
||||
return TextWithEntities();
|
||||
}
|
||||
return { qsl("[ ") + toString() + qsl(" ]"), EntitiesInText() };
|
||||
}
|
||||
|
||||
ImagePtr HistorySticker::replyPreview() {
|
||||
return _data->makeReplyPreview();
|
||||
}
|
||||
|
@ -3197,13 +3132,6 @@ HistoryTextState HistoryContact::getState(QPoint point, HistoryStateRequest requ
|
|||
return result;
|
||||
}
|
||||
|
||||
TextWithEntities HistoryContact::selectedText(TextSelection selection) const {
|
||||
if (selection != FullSelection) {
|
||||
return TextWithEntities();
|
||||
}
|
||||
return { qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") + _name.originalText() + '\n' + _phone, EntitiesInText() };
|
||||
}
|
||||
|
||||
HistoryCall::HistoryCall(
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::Call*> call)
|
||||
|
@ -3291,13 +3219,6 @@ HistoryTextState HistoryCall::getState(QPoint point, HistoryStateRequest request
|
|||
return result;
|
||||
}
|
||||
|
||||
TextWithEntities HistoryCall::selectedText(TextSelection selection) const {
|
||||
if (selection != FullSelection) {
|
||||
return TextWithEntities();
|
||||
}
|
||||
return { qsl("[ ") + _text + qsl(" ]"), EntitiesInText() };
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int articleThumbWidth(PhotoData *thumb, int height) {
|
||||
|
@ -3841,11 +3762,12 @@ bool HistoryWebPage::isDisplayed() const {
|
|||
}
|
||||
|
||||
TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
|
||||
if (selection == FullSelection && !isLogEntryOriginal()) {
|
||||
return TextWithEntities();
|
||||
}
|
||||
auto titleResult = _title.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection, ExpandLinksAll);
|
||||
auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection((selection == FullSelection) ? AllTextSelection : selection), ExpandLinksAll);
|
||||
auto titleResult = _title.originalTextWithEntities(
|
||||
selection,
|
||||
ExpandLinksAll);
|
||||
auto descriptionResult = _description.originalTextWithEntities(
|
||||
toDescriptionSelection(selection),
|
||||
ExpandLinksAll);
|
||||
if (titleResult.text.isEmpty()) {
|
||||
return descriptionResult;
|
||||
} else if (descriptionResult.text.isEmpty()) {
|
||||
|
@ -4237,11 +4159,12 @@ void HistoryGame::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pres
|
|||
}
|
||||
|
||||
TextWithEntities HistoryGame::selectedText(TextSelection selection) const {
|
||||
if (selection == FullSelection) {
|
||||
return TextWithEntities();
|
||||
}
|
||||
auto titleResult = _title.originalTextWithEntities(selection, ExpandLinksAll);
|
||||
auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection), ExpandLinksAll);
|
||||
auto titleResult = _title.originalTextWithEntities(
|
||||
selection,
|
||||
ExpandLinksAll);
|
||||
auto descriptionResult = _description.originalTextWithEntities(
|
||||
toDescriptionSelection(selection),
|
||||
ExpandLinksAll);
|
||||
if (titleResult.text.isEmpty()) {
|
||||
return descriptionResult;
|
||||
} else if (descriptionResult.text.isEmpty()) {
|
||||
|
@ -4637,11 +4560,12 @@ void HistoryInvoice::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool p
|
|||
}
|
||||
|
||||
TextWithEntities HistoryInvoice::selectedText(TextSelection selection) const {
|
||||
if (selection == FullSelection) {
|
||||
return TextWithEntities();
|
||||
}
|
||||
auto titleResult = _title.originalTextWithEntities(selection, ExpandLinksAll);
|
||||
auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection), ExpandLinksAll);
|
||||
auto titleResult = _title.originalTextWithEntities(
|
||||
selection,
|
||||
ExpandLinksAll);
|
||||
auto descriptionResult = _description.originalTextWithEntities(
|
||||
toDescriptionSelection(selection),
|
||||
ExpandLinksAll);
|
||||
if (titleResult.text.isEmpty()) {
|
||||
return descriptionResult;
|
||||
} else if (descriptionResult.text.isEmpty()) {
|
||||
|
@ -4685,12 +4609,11 @@ HistoryLocation::HistoryLocation(
|
|||
Ui::WebpageTextTitleOptions());
|
||||
}
|
||||
if (!description.isEmpty()) {
|
||||
auto marked = TextWithEntities { TextUtilities::Clean(description) };
|
||||
auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
|
||||
TextUtilities::ParseEntities(marked, parseFlags);
|
||||
_description.setMarkedText(
|
||||
st::webPageDescriptionStyle,
|
||||
marked,
|
||||
TextUtilities::ParseEntities(
|
||||
TextUtilities::Clean(description),
|
||||
TextParseLinks | TextParseMultiline | TextParseRichText),
|
||||
Ui::WebpageTextDescriptionOptions());
|
||||
}
|
||||
}
|
||||
|
@ -4924,17 +4847,6 @@ TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSele
|
|||
}
|
||||
|
||||
TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
|
||||
if (selection == FullSelection) {
|
||||
TextWithEntities result = { qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"), EntitiesInText() };
|
||||
auto info = selectedText(AllTextSelection);
|
||||
if (!info.text.isEmpty()) {
|
||||
TextUtilities::Append(result, std::move(info));
|
||||
result.text.append('\n');
|
||||
}
|
||||
result.text += _link->dragText();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto titleResult = _title.originalTextWithEntities(selection);
|
||||
auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection));
|
||||
if (titleResult.text.isEmpty()) {
|
||||
|
|
|
@ -41,11 +41,6 @@ namespace Ui {
|
|||
class EmptyUserpic;
|
||||
} // namespace Ui
|
||||
|
||||
TextWithEntities WithCaptionSelectedText(
|
||||
const QString &attachType,
|
||||
const Text &caption,
|
||||
TextSelection selection);
|
||||
|
||||
class HistoryFileMedia : public HistoryMedia {
|
||||
public:
|
||||
using HistoryMedia::HistoryMedia;
|
||||
|
@ -390,11 +385,6 @@ private:
|
|||
void setStatusSize(int newSize, qint64 realDuration = 0) const;
|
||||
bool updateStatusText() const; // returns showPause
|
||||
|
||||
// Callback is a void(const QString &, const QString &, const Text &) functor.
|
||||
// It will be called as callback(attachType, attachFileName, attachCaption).
|
||||
template <typename Callback>
|
||||
void buildStringRepresentation(Callback callback) const;
|
||||
|
||||
not_null<DocumentData*> _data;
|
||||
|
||||
};
|
||||
|
@ -523,8 +513,6 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
DocumentData *getDocument() const override {
|
||||
return _data;
|
||||
}
|
||||
|
@ -550,7 +538,6 @@ private:
|
|||
|
||||
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
|
||||
int additionalWidth() const;
|
||||
QString toString() const;
|
||||
|
||||
int _pixw = 1;
|
||||
int _pixh = 1;
|
||||
|
@ -584,8 +571,6 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
bool needsBubble() const override {
|
||||
return true;
|
||||
}
|
||||
|
@ -643,8 +628,6 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
bool needsBubble() const override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1017,6 +1017,13 @@ TextWithEntities HistoryMessage::originalText() const {
|
|||
return _text.originalTextWithEntities();
|
||||
}
|
||||
|
||||
TextWithEntities HistoryMessage::clipboardText() const {
|
||||
if (emptyText()) {
|
||||
return { QString(), EntitiesInText() };
|
||||
}
|
||||
return _text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
|
||||
}
|
||||
|
||||
bool HistoryMessage::textHasLinks() const {
|
||||
return emptyText() ? false : _text.hasLinks();
|
||||
}
|
||||
|
|
|
@ -121,6 +121,7 @@ public:
|
|||
|
||||
void setText(const TextWithEntities &textWithEntities) override;
|
||||
TextWithEntities originalText() const override;
|
||||
TextWithEntities clipboardText() const override;
|
||||
bool textHasLinks() const override;
|
||||
|
||||
int viewsCount() const override;
|
||||
|
|
|
@ -400,7 +400,10 @@ HistoryHider::~HistoryHider() {
|
|||
parent()->noHider(this);
|
||||
}
|
||||
|
||||
HistoryWidget::HistoryWidget(QWidget *parent, not_null<Window::Controller*> controller) : Window::AbstractSectionWidget(parent, controller)
|
||||
HistoryWidget::HistoryWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::Controller*> controller)
|
||||
: Window::AbstractSectionWidget(parent, controller)
|
||||
, _fieldBarCancel(this, st::historyReplyCancel)
|
||||
, _topBar(this, controller)
|
||||
, _scroll(this, st::historyScroll, false)
|
||||
|
@ -672,9 +675,21 @@ HistoryWidget::HistoryWidget(QWidget *parent, not_null<Window::Controller*> cont
|
|||
}
|
||||
}, lifetime());
|
||||
_topBar->membersShowAreaActive(
|
||||
) | rpl::start_with_next([this](bool active) {
|
||||
) | rpl::start_with_next([=](bool active) {
|
||||
setMembersShowAreaActive(active);
|
||||
}, _topBar->lifetime());
|
||||
_topBar->forwardSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
forwardSelected();
|
||||
}, _topBar->lifetime());
|
||||
_topBar->deleteSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
confirmDeleteSelected();
|
||||
}, _topBar->lifetime());
|
||||
_topBar->clearSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
clearSelected();
|
||||
}, _topBar->lifetime());
|
||||
|
||||
Auth().api().sendActions(
|
||||
) | rpl::start_with_next([this](const ApiWrap::SendOptions &options) {
|
||||
|
@ -1675,11 +1690,16 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
|
|||
Auth().data().stopAutoplayAnimations();
|
||||
}
|
||||
clearReplyReturns();
|
||||
|
||||
clearAllLoadRequests();
|
||||
|
||||
if (_history) {
|
||||
if (App::main()) App::main()->saveDraftToCloud();
|
||||
if (Ui::InFocusChain(_list)) {
|
||||
// Removing focus from list clears selected and updates top bar.
|
||||
setFocus();
|
||||
}
|
||||
if (App::main()) {
|
||||
App::main()->saveDraftToCloud();
|
||||
}
|
||||
if (_migrated) {
|
||||
_migrated->clearLocalDraft(); // use migrated draft only once
|
||||
_migrated->clearEditDraft();
|
||||
|
@ -1710,7 +1730,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
|
|||
_fieldBarCancel->hide();
|
||||
|
||||
_membersDropdownShowTimer.stop();
|
||||
_scroll->takeWidget<HistoryInner>().destroyDelayed();
|
||||
_scroll->takeWidget<HistoryInner>().destroy();
|
||||
_list = nullptr;
|
||||
|
||||
clearInlineBot();
|
||||
|
@ -3972,7 +3992,9 @@ void HistoryWidget::onFieldResize() {
|
|||
}
|
||||
|
||||
void HistoryWidget::onFieldFocused() {
|
||||
if (_list) _list->clearSelectedItems(true);
|
||||
if (_list) {
|
||||
_list->clearSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::onCheckFieldAutocomplete() {
|
||||
|
@ -6178,25 +6200,37 @@ void HistoryWidget::handlePeerUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::onForwardSelected() {
|
||||
if (!_list) return;
|
||||
auto weak = make_weak(this);
|
||||
void HistoryWidget::forwardSelected() {
|
||||
if (!_list) {
|
||||
return;
|
||||
}
|
||||
const auto weak = make_weak(this);
|
||||
Window::ShowForwardMessagesBox(getSelectedItems(), [=] {
|
||||
if (weak) {
|
||||
weak->onClearSelected();
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->clearSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HistoryWidget::confirmDeleteSelectedItems() {
|
||||
void HistoryWidget::confirmDeleteSelected() {
|
||||
if (!_list) return;
|
||||
|
||||
App::main()->deleteLayer(_list->getSelectedItems());
|
||||
auto items = _list->getSelectedItems();
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto weak = make_weak(this);
|
||||
const auto box = Ui::show(Box<DeleteMessagesBox>(std::move(items)));
|
||||
box->setDeleteConfirmedCallback([=] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->clearSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HistoryWidget::onListEscapePressed() {
|
||||
if (_nonEmptySelection && _list) {
|
||||
onClearSelected();
|
||||
clearSelected();
|
||||
} else {
|
||||
onCancel();
|
||||
}
|
||||
|
@ -6208,8 +6242,10 @@ void HistoryWidget::onListEnterPressed() {
|
|||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::onClearSelected() {
|
||||
if (_list) _list->clearSelectedItems();
|
||||
void HistoryWidget::clearSelected() {
|
||||
if (_list) {
|
||||
_list->clearSelected();
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem *HistoryWidget::getItemFromHistoryOrMigrated(MsgId genericMsgId) const {
|
||||
|
|
|
@ -324,9 +324,9 @@ public:
|
|||
void grapWithoutTopBarShadow();
|
||||
void grabFinish() override;
|
||||
|
||||
bool isItemVisible(HistoryItem *item);
|
||||
|
||||
void confirmDeleteSelectedItems();
|
||||
void forwardSelected();
|
||||
void confirmDeleteSelected();
|
||||
void clearSelected();
|
||||
|
||||
// Float player interface.
|
||||
bool wheelEventFromFloatPlayer(QEvent *e) override;
|
||||
|
@ -415,9 +415,6 @@ public slots:
|
|||
void onCheckFieldAutocomplete();
|
||||
void onScrollTimer();
|
||||
|
||||
void onForwardSelected();
|
||||
void onClearSelected();
|
||||
|
||||
void onDraftSaveDelayed();
|
||||
void onDraftSave(bool delayed = false);
|
||||
void onCloudDraftSave();
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "history/view/history_view_list_widget.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_text.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
|
@ -218,7 +219,9 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
|||
}
|
||||
if (!link && (view->hasVisibleText() || mediaHasTextForCopy)) {
|
||||
result->addAction(lang(lng_context_copy_text), [=] {
|
||||
SetClipboardWithEntities(list->getItemText(itemId));
|
||||
if (const auto item = App::histItemById(itemId)) {
|
||||
SetClipboardWithEntities(HistoryItemText(item));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ int Element::marginBottom() const {
|
|||
}
|
||||
|
||||
bool Element::isUnderCursor() const {
|
||||
return (App::hoveredItem() == this);
|
||||
return _delegate->elementUnderCursor(this);
|
||||
}
|
||||
|
||||
void Element::setPendingResize() {
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
not_null<HistoryMessage*> message) = 0;
|
||||
virtual std::unique_ptr<Element> elementCreate(
|
||||
not_null<HistoryService*> message) = 0;
|
||||
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
|
||||
virtual void elementAnimationAutoplayAsync(
|
||||
not_null<const Element*> element) = 0;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,18 @@ namespace HistoryView {
|
|||
|
||||
enum class Context : char;
|
||||
|
||||
struct SelectedItem {
|
||||
explicit SelectedItem(FullMsgId msgId) : msgId(msgId) {
|
||||
}
|
||||
|
||||
FullMsgId msgId;
|
||||
bool canDelete = false;
|
||||
bool canForward = false;
|
||||
|
||||
};
|
||||
|
||||
using SelectedItems = std::vector<SelectedItem>;
|
||||
|
||||
class ListDelegate {
|
||||
public:
|
||||
virtual Context listContext() = 0;
|
||||
|
@ -36,9 +48,25 @@ public:
|
|||
Data::MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) = 0;
|
||||
virtual bool listAllowsMultiSelect() = 0;
|
||||
virtual bool listIsLessInOrder(
|
||||
not_null<HistoryItem*> first,
|
||||
not_null<HistoryItem*> second) = 0;
|
||||
virtual void listSelectionChanged(SelectedItems &&items) = 0;
|
||||
|
||||
};
|
||||
|
||||
struct SelectionData {
|
||||
bool canDelete = false;
|
||||
bool canForward = false;
|
||||
|
||||
};
|
||||
|
||||
using SelectedMap = base::flat_map<
|
||||
FullMsgId,
|
||||
SelectionData,
|
||||
std::less<>>;
|
||||
|
||||
class ListMemento {
|
||||
public:
|
||||
struct ScrollTopState {
|
||||
|
@ -100,7 +128,8 @@ public:
|
|||
void restoreState(not_null<ListMemento*> memento);
|
||||
|
||||
TextWithEntities getSelectedText() const;
|
||||
TextWithEntities getItemText(FullMsgId itemId) const;
|
||||
MessageIdsList getSelectedItems() const;
|
||||
void cancelSelection();
|
||||
|
||||
// AbstractTooltipShower interface
|
||||
QString tooltipText() const override;
|
||||
|
@ -112,6 +141,7 @@ public:
|
|||
not_null<HistoryMessage*> message) override;
|
||||
std::unique_ptr<Element> elementCreate(
|
||||
not_null<HistoryService*> message) override;
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
void elementAnimationAutoplayAsync(
|
||||
not_null<const Element*> view) override;
|
||||
|
||||
|
@ -136,6 +166,21 @@ protected:
|
|||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
private:
|
||||
struct CursorState {
|
||||
FullMsgId itemId;
|
||||
int height = 0;
|
||||
QPoint cursor;
|
||||
bool inside = false;
|
||||
|
||||
inline bool operator==(const CursorState &other) const {
|
||||
return (itemId == other.itemId)
|
||||
&& (cursor == other.cursor);
|
||||
}
|
||||
inline bool operator!=(const CursorState &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
};
|
||||
enum class Direction {
|
||||
Up,
|
||||
Down,
|
||||
|
@ -144,12 +189,18 @@ private:
|
|||
None,
|
||||
PrepareDrag,
|
||||
Dragging,
|
||||
PrepareSelect,
|
||||
Selecting,
|
||||
};
|
||||
enum class EnumItemsDirection {
|
||||
TopToBottom,
|
||||
BottomToTop,
|
||||
};
|
||||
enum class DragSelectAction {
|
||||
None,
|
||||
Selecting,
|
||||
Deselecting,
|
||||
};
|
||||
using ScrollTopState = ListMemento::ScrollTopState;
|
||||
|
||||
void refreshViewer();
|
||||
|
@ -159,16 +210,23 @@ private:
|
|||
void saveScrollState();
|
||||
void restoreScrollState();
|
||||
|
||||
Element *viewForItem(FullMsgId itemId) const;
|
||||
Element *viewForItem(const HistoryItem *item) const;
|
||||
not_null<Element*> enforceViewForItem(not_null<HistoryItem*> item);
|
||||
|
||||
void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
|
||||
void mouseActionUpdate(const QPoint &screenPos);
|
||||
void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button);
|
||||
void mouseActionStart(
|
||||
const QPoint &globalPosition,
|
||||
Qt::MouseButton button);
|
||||
void mouseActionUpdate(const QPoint &globalPosition);
|
||||
void mouseActionUpdate();
|
||||
void mouseActionFinish(
|
||||
const QPoint &globalPosition,
|
||||
Qt::MouseButton button);
|
||||
void mouseActionCancel();
|
||||
void updateSelected();
|
||||
void performDrag();
|
||||
style::cursor computeMouseCursor() const;
|
||||
int itemTop(not_null<const Element*> view) const;
|
||||
void repaintItem(FullMsgId itemId);
|
||||
void repaintItem(const Element *view);
|
||||
void resizeItem(not_null<Element*> view);
|
||||
void refreshItem(not_null<const Element*> view);
|
||||
|
@ -176,7 +234,6 @@ private:
|
|||
QPoint mapPointToItem(QPoint point, const Element *view) const;
|
||||
|
||||
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
|
||||
void showStickerPackInfo(not_null<DocumentData*> document);
|
||||
|
||||
not_null<Element*> findItemByY(int y) const;
|
||||
Element *strictFindItemByY(int y) const;
|
||||
|
@ -197,6 +254,38 @@ private:
|
|||
void scrollDateCheck();
|
||||
void scrollDateHideByTimer();
|
||||
|
||||
void trySwitchToWordSelection();
|
||||
void switchToWordSelection();
|
||||
void validateTrippleClickStartTime();
|
||||
SelectedItems collectSelectedItems() const;
|
||||
MessageIdsList collectSelectedIds() const;
|
||||
void pushSelectedItems();
|
||||
void removeItemSelection(
|
||||
const SelectedMap::const_iterator &i);
|
||||
bool hasSelectedText() const;
|
||||
bool hasSelectedItems() const;
|
||||
void clearTextSelection();
|
||||
void clearSelected();
|
||||
void setTextSelection(
|
||||
not_null<Element*> view,
|
||||
TextSelection selection);
|
||||
bool applyItemSelection(SelectedMap &applyTo, FullMsgId itemId) const;
|
||||
void toggleItemSelection(FullMsgId itemId);
|
||||
SelectedMap::iterator itemUnderPressSelection();
|
||||
SelectedMap::const_iterator itemUnderPressSelection() const;
|
||||
bool isItemUnderPressSelected() const;
|
||||
bool requiredToStartDragging(not_null<Element*> view) const;
|
||||
bool isPressInSelectedText(HistoryTextState state) const;
|
||||
void updateDragSelection();
|
||||
void clearDragSelection();
|
||||
void applyDragSelection();
|
||||
void applyDragSelection(SelectedMap &applyTo) const;
|
||||
TextSelection itemRenderSelection(
|
||||
not_null<const Element*> view) const;
|
||||
TextSelection computeRenderSelection(
|
||||
not_null<const SelectedMap*> selected,
|
||||
not_null<const Element*> view) const;
|
||||
|
||||
// This function finds all history items that are displayed and calls template method
|
||||
// for each found message (in given direction) in the passed history with passed top offset.
|
||||
//
|
||||
|
@ -231,7 +320,10 @@ private:
|
|||
int _idsLimit = kMinimalIdsLimit;
|
||||
Data::MessagesSlice _slice;
|
||||
std::vector<not_null<Element*>> _items;
|
||||
std::map<not_null<HistoryItem*>, std::unique_ptr<Element>, std::less<>> _views;
|
||||
std::map<
|
||||
not_null<HistoryItem*>,
|
||||
std::unique_ptr<Element>,
|
||||
std::less<>> _views;
|
||||
int _itemsTop = 0;
|
||||
int _itemsWidth = 0;
|
||||
int _itemsHeight = 0;
|
||||
|
@ -252,22 +344,29 @@ private:
|
|||
|
||||
MouseAction _mouseAction = MouseAction::None;
|
||||
TextSelectType _mouseSelectType = TextSelectType::Letters;
|
||||
QPoint _dragStartPosition;
|
||||
QPoint _mousePosition;
|
||||
Element *_mouseActionItem = nullptr;
|
||||
CursorState _overState;
|
||||
CursorState _pressState;
|
||||
Element *_overItem = nullptr;
|
||||
HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
|
||||
uint16 _mouseTextSymbol = 0;
|
||||
bool _pressWasInactive = false;
|
||||
|
||||
Element *_selectedItem = nullptr;
|
||||
TextSelection _selectedText;
|
||||
bool _wasSelectedText = false; // was some text selected in current drag action
|
||||
bool _selectEnabled = false;
|
||||
HistoryItem *_selectedTextItem = nullptr;
|
||||
TextSelection _selectedTextRange;
|
||||
TextWithEntities _selectedText;
|
||||
SelectedMap _selected;
|
||||
base::flat_set<FullMsgId> _dragSelected;
|
||||
DragSelectAction _dragSelectAction = DragSelectAction::None;
|
||||
// Was some text selected in current drag action.
|
||||
bool _wasSelectedText = false;
|
||||
Qt::CursorShape _cursor = style::cur_default;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
QPoint _trippleClickPoint;
|
||||
base::Timer _trippleClickTimer;
|
||||
TimeMs _trippleClickStartTime = 0;
|
||||
|
||||
rpl::lifetime _viewerLifetime;
|
||||
|
||||
|
|
|
@ -970,13 +970,8 @@ TextWithEntities Message::selectedText(TextSelection selection) const {
|
|||
const auto media = this->media();
|
||||
|
||||
TextWithEntities logEntryOriginalResult;
|
||||
const auto textSelection = (selection == FullSelection)
|
||||
? AllTextSelection
|
||||
: IsSubGroupSelection(selection)
|
||||
? TextSelection(0, 0)
|
||||
: selection;
|
||||
auto textResult = item->_text.originalTextWithEntities(
|
||||
textSelection,
|
||||
selection,
|
||||
ExpandLinksAll);
|
||||
auto skipped = skipTextSelection(selection);
|
||||
auto mediaDisplayed = (media && media->isDisplayed());
|
||||
|
@ -1002,28 +997,6 @@ TextWithEntities Message::selectedText(TextSelection selection) const {
|
|||
result.text += qstr("\n\n");
|
||||
TextUtilities::Append(result, std::move(logEntryOriginalResult));
|
||||
}
|
||||
if (auto reply = item->Get<HistoryMessageReply>()) {
|
||||
if (selection == FullSelection && reply->replyToMsg) {
|
||||
TextWithEntities wrapped;
|
||||
wrapped.text.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.text.size());
|
||||
wrapped.text.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n"));
|
||||
TextUtilities::Append(wrapped, std::move(result));
|
||||
result = wrapped;
|
||||
}
|
||||
}
|
||||
if (auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||
if (selection == FullSelection) {
|
||||
auto fwdinfo = forwarded->text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
|
||||
auto wrapped = TextWithEntities();
|
||||
wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
|
||||
wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.size());
|
||||
wrapped.text.append('[');
|
||||
TextUtilities::Append(wrapped, std::move(fwdinfo));
|
||||
wrapped.text.append(qsl("]\n"));
|
||||
TextUtilities::Append(wrapped, std::move(result));
|
||||
result = wrapped;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -508,8 +508,7 @@ void Service::updatePressed(QPoint point) {
|
|||
}
|
||||
|
||||
TextWithEntities Service::selectedText(TextSelection selection) const {
|
||||
return message()->_text.originalTextWithEntities(
|
||||
(selection == FullSelection) ? AllTextSelection : selection);
|
||||
return message()->_text.originalTextWithEntities(selection);
|
||||
}
|
||||
|
||||
TextSelection Service::adjustSelection(
|
||||
|
|
|
@ -43,7 +43,7 @@ TopBarWidget::TopBarWidget(
|
|||
not_null<Window::Controller*> controller)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _clearSelection(this, langFactory(lng_selected_clear), st::topBarClearButton)
|
||||
, _clear(this, langFactory(lng_selected_clear), st::topBarClearButton)
|
||||
, _forward(this, langFactory(lng_selected_forward), st::defaultActiveButton)
|
||||
, _delete(this, langFactory(lng_selected_delete), st::defaultActiveButton)
|
||||
, _back(this, st::historyTopBarBack)
|
||||
|
@ -55,11 +55,11 @@ TopBarWidget::TopBarWidget(
|
|||
subscribe(Lang::Current().updated(), [this] { refreshLang(); });
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
_forward->setClickedCallback([this] { onForwardSelection(); });
|
||||
_forward->setClickedCallback([this] { _forwardSelection.fire({}); });
|
||||
_forward->setWidthChangedCallback([this] { updateControlsGeometry(); });
|
||||
_delete->setClickedCallback([this] { onDeleteSelection(); });
|
||||
_delete->setClickedCallback([this] { _deleteSelection.fire({}); });
|
||||
_delete->setWidthChangedCallback([this] { updateControlsGeometry(); });
|
||||
_clearSelection->setClickedCallback([this] { onClearSelection(); });
|
||||
_clear->setClickedCallback([this] { _clearSelection.fire({}); });
|
||||
_call->setClickedCallback([this] { onCall(); });
|
||||
_search->setClickedCallback([this] { onSearch(); });
|
||||
_menuToggle->setClickedCallback([this] { showMenu(); });
|
||||
|
@ -132,18 +132,6 @@ void TopBarWidget::refreshLang() {
|
|||
InvokeQueued(this, [this] { updateControlsGeometry(); });
|
||||
}
|
||||
|
||||
void TopBarWidget::onForwardSelection() {
|
||||
if (App::main()) App::main()->forwardSelectedItems();
|
||||
}
|
||||
|
||||
void TopBarWidget::onDeleteSelection() {
|
||||
if (App::main()) App::main()->confirmDeleteSelectedItems();
|
||||
}
|
||||
|
||||
void TopBarWidget::onClearSelection() {
|
||||
if (App::main()) App::main()->clearSelectedItems();
|
||||
}
|
||||
|
||||
void TopBarWidget::onSearch() {
|
||||
if (_activeChat) {
|
||||
App::main()->searchInChat(_activeChat);
|
||||
|
@ -399,7 +387,7 @@ void TopBarWidget::updateControlsGeometry() {
|
|||
auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.current(hasSelected ? 1. : 0.));
|
||||
auto otherButtonsTop = selectedButtonsTop + st::topBarHeight;
|
||||
auto buttonsLeft = st::topBarActionSkip + (Adaptive::OneColumn() ? 0 : st::lineWidth);
|
||||
auto buttonsWidth = _forward->contentWidth() + _delete->contentWidth() + _clearSelection->width();
|
||||
auto buttonsWidth = _forward->contentWidth() + _delete->contentWidth() + _clear->width();
|
||||
buttonsWidth += buttonsLeft + st::topBarActionSkip * 3;
|
||||
|
||||
auto widthLeft = qMin(width() - buttonsWidth, -2 * st::defaultActiveButton.width);
|
||||
|
@ -414,7 +402,7 @@ void TopBarWidget::updateControlsGeometry() {
|
|||
}
|
||||
|
||||
_delete->moveToLeft(buttonsLeft, selectedButtonsTop);
|
||||
_clearSelection->moveToRight(st::topBarActionSkip, selectedButtonsTop);
|
||||
_clear->moveToRight(st::topBarActionSkip, selectedButtonsTop);
|
||||
|
||||
if (_unreadBadge) {
|
||||
_unreadBadge->setGeometryToLeft(
|
||||
|
@ -469,7 +457,7 @@ void TopBarWidget::updateControlsVisibility() {
|
|||
hideChildren();
|
||||
return;
|
||||
}
|
||||
_clearSelection->show();
|
||||
_clear->show();
|
||||
_delete->setVisible(_canDelete);
|
||||
_forward->setVisible(_canForward);
|
||||
|
||||
|
|
|
@ -48,6 +48,16 @@ public:
|
|||
|
||||
void setActiveChat(Dialogs::Key chat);
|
||||
|
||||
rpl::producer<> forwardSelectionRequest() const {
|
||||
return _forwardSelection.events();
|
||||
}
|
||||
rpl::producer<> deleteSelectionRequest() const {
|
||||
return _deleteSelection.events();
|
||||
}
|
||||
rpl::producer<> clearSelectionRequest() const {
|
||||
return _clearSelection.events();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
|
@ -62,9 +72,6 @@ private:
|
|||
void selectedShowCallback();
|
||||
void updateInfoToggleActive();
|
||||
|
||||
void onForwardSelection();
|
||||
void onDeleteSelection();
|
||||
void onClearSelection();
|
||||
void onCall();
|
||||
void onSearch();
|
||||
void showMenu();
|
||||
|
@ -95,7 +102,7 @@ private:
|
|||
|
||||
Animation _selectedShown;
|
||||
|
||||
object_ptr<Ui::RoundButton> _clearSelection;
|
||||
object_ptr<Ui::RoundButton> _clear;
|
||||
object_ptr<Ui::RoundButton> _forward, _delete;
|
||||
|
||||
object_ptr<Ui::IconButton> _back;
|
||||
|
@ -121,6 +128,10 @@ private:
|
|||
int _unreadCounterSubscription = 0;
|
||||
base::Timer _onlineUpdater;
|
||||
|
||||
rpl::event_stream<> _forwardSelection;
|
||||
rpl::event_stream<> _deleteSelection;
|
||||
rpl::event_stream<> _clearSelection;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -509,11 +509,13 @@ void TopBar::performForward() {
|
|||
_cancelSelectionClicks.fire({});
|
||||
return;
|
||||
}
|
||||
Window::ShowForwardMessagesBox(std::move(items), [weak = make_weak(this)]{
|
||||
if (weak) {
|
||||
weak->_cancelSelectionClicks.fire({});
|
||||
}
|
||||
});
|
||||
Window::ShowForwardMessagesBox(
|
||||
std::move(items),
|
||||
[weak = make_weak(this)] {
|
||||
if (weak) {
|
||||
weak->_cancelSelectionClicks.fire({});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void TopBar::performDelete() {
|
||||
|
@ -521,7 +523,12 @@ void TopBar::performDelete() {
|
|||
if (items.empty()) {
|
||||
_cancelSelectionClicks.fire({});
|
||||
} else {
|
||||
Ui::show(Box<DeleteMessagesBox>(std::move(items)));
|
||||
const auto box = Ui::show(Box<DeleteMessagesBox>(std::move(items)));
|
||||
box->setDeleteConfirmedCallback([weak = make_weak(this)] {
|
||||
if (weak) {
|
||||
weak->_cancelSelectionClicks.fire({});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1387,7 +1387,14 @@ void ListWidget::forwardItems(MessageIdsList &&items) {
|
|||
}
|
||||
|
||||
void ListWidget::deleteSelected() {
|
||||
deleteItems(collectSelectedIds());
|
||||
if (const auto box = deleteItems(collectSelectedIds())) {
|
||||
const auto weak = make_weak(this);
|
||||
box->setDeleteConfirmedCallback([=]{
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->clearSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::deleteItem(UniversalMsgId universalId) {
|
||||
|
@ -1396,11 +1403,14 @@ void ListWidget::deleteItem(UniversalMsgId universalId) {
|
|||
}
|
||||
}
|
||||
|
||||
void ListWidget::deleteItems(MessageIdsList &&items) {
|
||||
DeleteMessagesBox *ListWidget::deleteItems(MessageIdsList &&items) {
|
||||
if (!items.empty()) {
|
||||
const auto box = Ui::show(Box<DeleteMessagesBox>(std::move(items)));
|
||||
setActionBoxWeak(box.data());
|
||||
const auto box = Ui::show(
|
||||
Box<DeleteMessagesBox>(std::move(items))).data();
|
||||
setActionBoxWeak(box);
|
||||
return box;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ListWidget::setActionBoxWeak(QPointer<Ui::RpWidget> box) {
|
||||
|
@ -1575,7 +1585,7 @@ void ListWidget::enterEventHook(QEvent *e) {
|
|||
}
|
||||
|
||||
void ListWidget::leaveEventHook(QEvent *e) {
|
||||
if (auto item = _overLayout) {
|
||||
if (const auto item = _overLayout) {
|
||||
if (_overState.inside) {
|
||||
repaintItem(item);
|
||||
_overState.inside = false;
|
||||
|
@ -1596,12 +1606,12 @@ QPoint ListWidget::clampMousePosition(QPoint position) const {
|
|||
};
|
||||
}
|
||||
|
||||
void ListWidget::mouseActionUpdate(const QPoint &screenPos) {
|
||||
void ListWidget::mouseActionUpdate(const QPoint &globalPosition) {
|
||||
if (_sections.empty() || _visibleBottom <= _visibleTop) {
|
||||
return;
|
||||
}
|
||||
|
||||
_mousePosition = screenPos;
|
||||
_mousePosition = globalPosition;
|
||||
|
||||
auto local = mapFromGlobal(_mousePosition);
|
||||
auto point = clampMousePosition(local);
|
||||
|
@ -1625,14 +1635,14 @@ void ListWidget::mouseActionUpdate(const QPoint &screenPos) {
|
|||
auto inTextSelection = _overState.inside
|
||||
&& (_overState.itemId == _pressState.itemId)
|
||||
&& hasSelectedText();
|
||||
auto cursorDeltaLength = [&] {
|
||||
auto cursorDelta = (_overState.cursor - _pressState.cursor);
|
||||
return cursorDelta.manhattanLength();
|
||||
};
|
||||
auto dragStartLength = [] {
|
||||
return QApplication::startDragDistance();
|
||||
};
|
||||
if (_overLayout) {
|
||||
auto cursorDeltaLength = [&] {
|
||||
auto cursorDelta = (_overState.cursor - _pressState.cursor);
|
||||
return cursorDelta.manhattanLength();
|
||||
};
|
||||
auto dragStartLength = [] {
|
||||
return QApplication::startDragDistance();
|
||||
};
|
||||
if (_overState.itemId != _pressState.itemId
|
||||
|| cursorDeltaLength() >= dragStartLength()) {
|
||||
if (_mouseAction == MouseAction::PrepareDrag) {
|
||||
|
@ -1770,9 +1780,13 @@ void ListWidget::clearDragSelection() {
|
|||
}
|
||||
}
|
||||
|
||||
void ListWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton button) {
|
||||
mouseActionUpdate(screenPos);
|
||||
if (button != Qt::LeftButton) return;
|
||||
void ListWidget::mouseActionStart(
|
||||
const QPoint &globalPosition,
|
||||
Qt::MouseButton button) {
|
||||
mouseActionUpdate(globalPosition);
|
||||
if (button != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClickHandler::pressed();
|
||||
if (_pressState != _overState) {
|
||||
|
@ -1799,9 +1813,9 @@ void ListWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butto
|
|||
}
|
||||
}
|
||||
if (_mouseAction == MouseAction::None && pressLayout) {
|
||||
HistoryTextState dragState;
|
||||
validateTrippleClickStartTime();
|
||||
auto startDistance = (screenPos - _trippleClickPoint).manhattanLength();
|
||||
HistoryTextState dragState;
|
||||
auto startDistance = (globalPosition - _trippleClickPoint).manhattanLength();
|
||||
auto validStartPoint = startDistance < QApplication::startDragDistance();
|
||||
if (_trippleClickStartTime != 0 && validStartPoint) {
|
||||
HistoryStateRequest request;
|
||||
|
@ -1950,8 +1964,10 @@ void ListWidget::performDrag() {
|
|||
//}
|
||||
}
|
||||
|
||||
void ListWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
|
||||
mouseActionUpdate(screenPos);
|
||||
void ListWidget::mouseActionFinish(
|
||||
const QPoint &globalPosition,
|
||||
Qt::MouseButton button) {
|
||||
mouseActionUpdate(globalPosition);
|
||||
|
||||
auto pressState = base::take(_pressState);
|
||||
repaintItem(pressState.itemId);
|
||||
|
|
|
@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_shared_media.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
|
||||
class DeleteMessagesBox;
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
@ -186,7 +188,7 @@ private:
|
|||
void forwardItems(MessageIdsList &&items);
|
||||
void deleteSelected();
|
||||
void deleteItem(UniversalMsgId universalId);
|
||||
void deleteItems(MessageIdsList &&items);
|
||||
DeleteMessagesBox *deleteItems(MessageIdsList &&items);
|
||||
void applyItemSelection(
|
||||
UniversalMsgId universalId,
|
||||
TextSelection selection);
|
||||
|
@ -233,12 +235,12 @@ private:
|
|||
|
||||
QPoint clampMousePosition(QPoint position) const;
|
||||
void mouseActionStart(
|
||||
const QPoint &screenPos,
|
||||
const QPoint &globalPosition,
|
||||
Qt::MouseButton button);
|
||||
void mouseActionUpdate(const QPoint &screenPos);
|
||||
void mouseActionUpdate(const QPoint &globalPosition);
|
||||
void mouseActionUpdate();
|
||||
void mouseActionFinish(
|
||||
const QPoint &screenPos,
|
||||
const QPoint &globalPosition,
|
||||
Qt::MouseButton button);
|
||||
void mouseActionCancel();
|
||||
void performDrag();
|
||||
|
|
|
@ -877,12 +877,6 @@ void MainWidget::showSendPathsLayer() {
|
|||
hiderLayer(object_ptr<HistoryHider>(this));
|
||||
}
|
||||
|
||||
void MainWidget::deleteLayer(MessageIdsList &&items) {
|
||||
if (!items.empty()) {
|
||||
Ui::show(Box<DeleteMessagesBox>(std::move(items)));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::deleteLayer(FullMsgId itemId) {
|
||||
if (const auto item = App::histItemById(itemId)) {
|
||||
const auto suggestModerateActions = true;
|
||||
|
@ -1349,18 +1343,6 @@ void MainWidget::onCacheBackground() {
|
|||
_cachedFor = _willCacheFor;
|
||||
}
|
||||
|
||||
void MainWidget::forwardSelectedItems() {
|
||||
_history->onForwardSelected();
|
||||
}
|
||||
|
||||
void MainWidget::confirmDeleteSelectedItems() {
|
||||
_history->confirmDeleteSelectedItems();
|
||||
}
|
||||
|
||||
void MainWidget::clearSelectedItems() {
|
||||
_history->onClearSelected();
|
||||
}
|
||||
|
||||
Dialogs::IndexedList *MainWidget::contactsList() {
|
||||
return _dialogs->contactsList();
|
||||
}
|
||||
|
|
|
@ -168,7 +168,6 @@ public:
|
|||
|
||||
void showForwardLayer(MessageIdsList &&items);
|
||||
void showSendPathsLayer();
|
||||
void deleteLayer(MessageIdsList &&items);
|
||||
void deleteLayer(FullMsgId itemId);
|
||||
void cancelUploadLayer(not_null<HistoryItem*> item);
|
||||
void shareUrlLayer(const QString &url, const QString &text);
|
||||
|
@ -221,10 +220,6 @@ public:
|
|||
|
||||
bool sendMessageFail(const RPCError &error);
|
||||
|
||||
void forwardSelectedItems();
|
||||
void confirmDeleteSelectedItems();
|
||||
void clearSelectedItems();
|
||||
|
||||
Dialogs::IndexedList *contactsList();
|
||||
Dialogs::IndexedList *dialogsList();
|
||||
Dialogs::IndexedList *contactsNoDialogsList();
|
||||
|
|
|
@ -1780,6 +1780,13 @@ void ParseMarkdown(
|
|||
}
|
||||
}
|
||||
|
||||
TextWithEntities ParseEntities(const QString &text, int32 flags) {
|
||||
const auto rich = ((flags & TextParseRichText) != 0);
|
||||
auto result = TextWithEntities{ text, EntitiesInText() };
|
||||
ParseEntities(result, flags, rich);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Some code is duplicated in flattextarea.cpp!
|
||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
||||
if (flags & TextParseMarkdown) { // parse markdown entities (bold, italic, code and pre)
|
||||
|
|
|
@ -214,6 +214,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, Conver
|
|||
|
||||
// New entities are added to the ones that are already in result.
|
||||
// Changes text if (flags & TextParseMarkdown).
|
||||
TextWithEntities ParseEntities(const QString &text, int32 flags);
|
||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
|
||||
QString ApplyEntities(const TextWithEntities &text);
|
||||
|
||||
|
|
|
@ -349,7 +349,7 @@ void Filler::addChatActions(not_null<ChatData*> chat) {
|
|||
|
||||
void Filler::addChannelActions(not_null<ChannelData*> channel) {
|
||||
auto isGroup = channel->isMegagroup();
|
||||
if (false && !isGroup) {
|
||||
if (!isGroup) {
|
||||
const auto grouped = (channel->feed() != nullptr);
|
||||
_addAction(
|
||||
lang(grouped ? lng_feed_ungroup : lng_feed_group),
|
||||
|
|
|
@ -252,6 +252,8 @@
|
|||
<(src_loc)/history/history_item.h
|
||||
<(src_loc)/history/history_item_components.cpp
|
||||
<(src_loc)/history/history_item_components.h
|
||||
<(src_loc)/history/history_item_text.cpp
|
||||
<(src_loc)/history/history_item_text.h
|
||||
<(src_loc)/history/history_inner_widget.cpp
|
||||
<(src_loc)/history/history_inner_widget.h
|
||||
<(src_loc)/history/history_location_manager.cpp
|
||||
|
|
Loading…
Add table
Reference in a new issue