mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support selecting quote in reply info edit.
This commit is contained in:
parent
aad157cf56
commit
d62fb5786d
21 changed files with 704 additions and 132 deletions
|
@ -653,6 +653,8 @@ PRIVATE
|
||||||
history/view/controls/history_view_compose_search.h
|
history/view/controls/history_view_compose_search.h
|
||||||
history/view/controls/history_view_forward_panel.cpp
|
history/view/controls/history_view_forward_panel.cpp
|
||||||
history/view/controls/history_view_forward_panel.h
|
history/view/controls/history_view_forward_panel.h
|
||||||
|
history/view/controls/history_view_reply_options.cpp
|
||||||
|
history/view/controls/history_view_reply_options.h
|
||||||
history/view/controls/history_view_ttl_button.cpp
|
history/view/controls/history_view_ttl_button.cpp
|
||||||
history/view/controls/history_view_ttl_button.h
|
history/view/controls/history_view_ttl_button.h
|
||||||
history/view/controls/history_view_voice_record_bar.cpp
|
history/view/controls/history_view_voice_record_bar.cpp
|
||||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history_item_helpers.h"
|
#include "history/history_item_helpers.h"
|
||||||
#include "history/view/controls/history_view_forward_panel.h"
|
#include "history/view/controls/history_view_forward_panel.h"
|
||||||
|
#include "history/view/controls/history_view_reply_options.h"
|
||||||
#include "history/view/media/history_view_media.h"
|
#include "history/view/media/history_view_media.h"
|
||||||
#include "history/view/media/history_view_sticker.h"
|
#include "history/view/media/history_view_sticker.h"
|
||||||
#include "history/view/media/history_view_web_page.h"
|
#include "history/view/media/history_view_web_page.h"
|
||||||
|
|
|
@ -82,6 +82,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_unread_things.h"
|
#include "history/history_unread_things.h"
|
||||||
#include "history/view/controls/history_view_compose_search.h"
|
#include "history/view/controls/history_view_compose_search.h"
|
||||||
#include "history/view/controls/history_view_forward_panel.h"
|
#include "history/view/controls/history_view_forward_panel.h"
|
||||||
|
#include "history/view/controls/history_view_reply_options.h"
|
||||||
#include "history/view/controls/history_view_voice_record_bar.h"
|
#include "history/view/controls/history_view_voice_record_bar.h"
|
||||||
#include "history/view/controls/history_view_ttl_button.h"
|
#include "history/view/controls/history_view_ttl_button.h"
|
||||||
#include "history/view/reactions/history_view_reactions_button.h"
|
#include "history/view/reactions/history_view_reactions_button.h"
|
||||||
|
@ -6235,6 +6236,9 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
|
||||||
_forwardPanel->editOptions(controller()->uiShow());
|
_forwardPanel->editOptions(controller()->uiShow());
|
||||||
}
|
}
|
||||||
} else if (const auto reply = replyTo()) {
|
} else if (const auto reply = replyTo()) {
|
||||||
|
const auto done = [=](FullReplyTo replyTo) {
|
||||||
|
replyToMessage(replyTo);
|
||||||
|
};
|
||||||
const auto highlight = [=] {
|
const auto highlight = [=] {
|
||||||
controller()->showPeerHistory(
|
controller()->showPeerHistory(
|
||||||
reply.messageId.peer,
|
reply.messageId.peer,
|
||||||
|
@ -6246,6 +6250,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
|
||||||
EditReplyOptions(
|
EditReplyOptions(
|
||||||
controller()->uiShow(),
|
controller()->uiShow(),
|
||||||
reply,
|
reply,
|
||||||
|
done,
|
||||||
highlight,
|
highlight,
|
||||||
[=] { ClearDraftReplyTo(history, reply.messageId); });
|
[=] { ClearDraftReplyTo(history, reply.messageId); });
|
||||||
} else if (_editMsgId) {
|
} else if (_editMsgId) {
|
||||||
|
|
|
@ -46,9 +46,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/power_saving.h"
|
#include "ui/power_saving.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "history/view/controls/history_view_forward_panel.h"
|
||||||
|
#include "history/view/controls/history_view_reply_options.h"
|
||||||
#include "history/view/controls/history_view_voice_record_bar.h"
|
#include "history/view/controls/history_view_voice_record_bar.h"
|
||||||
#include "history/view/controls/history_view_ttl_button.h"
|
#include "history/view/controls/history_view_ttl_button.h"
|
||||||
#include "history/view/controls/history_view_forward_panel.h"
|
|
||||||
#include "history/view/history_view_webpage_preview.h"
|
#include "history/view/history_view_webpage_preview.h"
|
||||||
#include "inline_bots/bot_attach_web_view.h"
|
#include "inline_bots/bot_attach_web_view.h"
|
||||||
#include "inline_bots/inline_results_widget.h"
|
#include "inline_bots/inline_results_widget.h"
|
||||||
|
@ -624,12 +625,16 @@ void FieldHeader::init() {
|
||||||
};
|
};
|
||||||
const auto history = _history;
|
const auto history = _history;
|
||||||
const auto topicRootId = _topicRootId;
|
const auto topicRootId = _topicRootId;
|
||||||
|
const auto done = [=](FullReplyTo replyTo) {
|
||||||
|
replyToMessage(replyTo);
|
||||||
|
};
|
||||||
const auto clearOldReplyTo = [=, id = reply.messageId] {
|
const auto clearOldReplyTo = [=, id = reply.messageId] {
|
||||||
ClearDraftReplyTo(history, topicRootId, id);
|
ClearDraftReplyTo(history, topicRootId, id);
|
||||||
};
|
};
|
||||||
EditReplyOptions(
|
EditReplyOptions(
|
||||||
_show,
|
_show,
|
||||||
reply,
|
reply,
|
||||||
|
done,
|
||||||
highlight,
|
highlight,
|
||||||
clearOldReplyTo);
|
clearOldReplyTo);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -428,125 +428,6 @@ void ClearDraftReplyTo(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowReplyToChatBox(
|
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
|
||||||
FullReplyTo reply,
|
|
||||||
Fn<void()> clearOldDraft) {
|
|
||||||
class Controller final : public ChooseRecipientBoxController {
|
|
||||||
public:
|
|
||||||
using Chosen = not_null<Data::Thread*>;
|
|
||||||
|
|
||||||
Controller(not_null<Main::Session*> session)
|
|
||||||
: ChooseRecipientBoxController(
|
|
||||||
session,
|
|
||||||
[=](Chosen thread) mutable { _singleChosen.fire_copy(thread); },
|
|
||||||
nullptr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void rowClicked(not_null<PeerListRow*> row) override final {
|
|
||||||
ChooseRecipientBoxController::rowClicked(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<Chosen> singleChosen() const{
|
|
||||||
return _singleChosen.events();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool respectSavedMessagesChat() const override {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void prepareViewHook() override {
|
|
||||||
delegate()->peerListSetTitle(rpl::single(u"Reply in..."_q));
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::event_stream<Chosen> _singleChosen;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
not_null<PeerListBox*> box;
|
|
||||||
not_null<Controller*> controller;
|
|
||||||
base::unique_qptr<Ui::PopupMenu> menu;
|
|
||||||
};
|
|
||||||
const auto session = &show->session();
|
|
||||||
const auto state = [&] {
|
|
||||||
auto controller = std::make_unique<Controller>(session);
|
|
||||||
const auto controllerRaw = controller.get();
|
|
||||||
auto box = Box<PeerListBox>(std::move(controller), nullptr);
|
|
||||||
const auto boxRaw = box.data();
|
|
||||||
show->show(std::move(box));
|
|
||||||
auto state = State{ boxRaw, controllerRaw };
|
|
||||||
return boxRaw->lifetime().make_state<State>(std::move(state));
|
|
||||||
}();
|
|
||||||
|
|
||||||
auto chosen = [=](not_null<Data::Thread*> thread) mutable {
|
|
||||||
const auto history = thread->owningHistory();
|
|
||||||
const auto topicRootId = thread->topicRootId();
|
|
||||||
const auto draft = history->localDraft(topicRootId);
|
|
||||||
const auto textWithTags = draft
|
|
||||||
? draft->textWithTags
|
|
||||||
: TextWithTags();
|
|
||||||
const auto cursor = draft ? draft->cursor : MessageCursor();
|
|
||||||
reply.topicRootId = topicRootId;
|
|
||||||
history->setLocalDraft(std::make_unique<Data::Draft>(
|
|
||||||
textWithTags,
|
|
||||||
reply,
|
|
||||||
cursor,
|
|
||||||
Data::WebPageDraft()));
|
|
||||||
history->clearLocalEditDraft(topicRootId);
|
|
||||||
history->session().changes().entryUpdated(
|
|
||||||
thread,
|
|
||||||
Data::EntryUpdate::Flag::LocalDraftSet);
|
|
||||||
|
|
||||||
if (clearOldDraft) {
|
|
||||||
crl::on_main(&history->session(), clearOldDraft);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
auto callback = [=, chosen = std::move(chosen)](
|
|
||||||
Controller::Chosen thread) mutable {
|
|
||||||
const auto weak = Ui::MakeWeak(state->box);
|
|
||||||
if (!chosen(thread)) {
|
|
||||||
return;
|
|
||||||
} else if (const auto strong = weak.data()) {
|
|
||||||
strong->closeBox();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
state->controller->singleChosen(
|
|
||||||
) | rpl::start_with_next(std::move(callback), state->box->lifetime());
|
|
||||||
}
|
|
||||||
|
|
||||||
void EditReplyOptions(
|
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
|
||||||
FullReplyTo reply,
|
|
||||||
Fn<void()> highlight,
|
|
||||||
Fn<void()> clearOldDraft) {
|
|
||||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
|
||||||
box->setTitle(rpl::single(u"Reply to Message"_q));
|
|
||||||
|
|
||||||
Settings::AddButton(
|
|
||||||
box->verticalLayout(),
|
|
||||||
rpl::single(u"Reply in another chat"_q),
|
|
||||||
st::settingsButton,
|
|
||||||
{ &st::menuIconReply }
|
|
||||||
)->setClickedCallback([=] {
|
|
||||||
ShowReplyToChatBox(show, reply, clearOldDraft);
|
|
||||||
});
|
|
||||||
|
|
||||||
Settings::AddButton(
|
|
||||||
box->verticalLayout(),
|
|
||||||
rpl::single(u"Show message"_q),
|
|
||||||
st::settingsButton,
|
|
||||||
{ &st::menuIconShowInChat }
|
|
||||||
)->setClickedCallback(highlight);
|
|
||||||
|
|
||||||
box->addButton(tr::lng_box_ok(), [=] {
|
|
||||||
box->closeBox();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EditWebPageOptions(
|
void EditWebPageOptions(
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
not_null<WebPageData*> webpage,
|
not_null<WebPageData*> webpage,
|
||||||
|
|
|
@ -78,17 +78,6 @@ void ClearDraftReplyTo(
|
||||||
MsgId topicRootId,
|
MsgId topicRootId,
|
||||||
FullMsgId equalTo);
|
FullMsgId equalTo);
|
||||||
|
|
||||||
void ShowReplyToChatBox(
|
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
|
||||||
FullReplyTo reply,
|
|
||||||
Fn<void()> clearOldDraft = nullptr);
|
|
||||||
|
|
||||||
void EditReplyOptions(
|
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
|
||||||
FullReplyTo reply,
|
|
||||||
Fn<void()> highlight,
|
|
||||||
Fn<void()> clearOldDraft = nullptr);
|
|
||||||
|
|
||||||
void EditWebPageOptions(
|
void EditWebPageOptions(
|
||||||
std::shared_ptr<ChatHelpers::Show> show,
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
not_null<WebPageData*> webpage,
|
not_null<WebPageData*> webpage,
|
||||||
|
|
|
@ -0,0 +1,496 @@
|
||||||
|
/*
|
||||||
|
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/view/controls/history_view_reply_options.h"
|
||||||
|
|
||||||
|
#include "boxes/peer_list_box.h"
|
||||||
|
#include "boxes/peer_list_controllers.h"
|
||||||
|
#include "chat_helpers/compose/compose_show.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_drafts.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_thread.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
|
#include "history/view/history_view_element.h"
|
||||||
|
#include "history/view/history_view_cursor_state.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "settings/settings_common.h"
|
||||||
|
#include "ui/chat/chat_style.h"
|
||||||
|
#include "ui/chat/chat_theme.h"
|
||||||
|
#include "ui/effects/path_shift_gradient.h"
|
||||||
|
#include "ui/layers/generic_box.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "window/themes/window_theme.h"
|
||||||
|
#include "window/section_widget.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
#include "styles/style_menu_icons.h"
|
||||||
|
#include "styles/style_settings.h"
|
||||||
|
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
#include <QtWidgets/QWidget>
|
||||||
|
|
||||||
|
namespace HistoryView::Controls {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class PreviewDelegate final : public DefaultElementDelegate {
|
||||||
|
public:
|
||||||
|
PreviewDelegate(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
not_null<Ui::ChatStyle*> st,
|
||||||
|
Fn<void()> update);
|
||||||
|
|
||||||
|
bool elementAnimationsPaused() override;
|
||||||
|
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||||
|
Context elementContext() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<QWidget*> _parent;
|
||||||
|
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::unique_ptr<Ui::ChatTheme> DefaultThemeOn(
|
||||||
|
rpl::lifetime &lifetime) {
|
||||||
|
auto result = std::make_unique<Ui::ChatTheme>();
|
||||||
|
|
||||||
|
using namespace Window::Theme;
|
||||||
|
const auto push = [=, raw = result.get()] {
|
||||||
|
const auto background = Background();
|
||||||
|
const auto &paper = background->paper();
|
||||||
|
raw->setBackground({
|
||||||
|
.prepared = background->prepared(),
|
||||||
|
.preparedForTiled = background->preparedForTiled(),
|
||||||
|
.gradientForFill = background->gradientForFill(),
|
||||||
|
.colorForFill = background->colorForFill(),
|
||||||
|
.colors = paper.backgroundColors(),
|
||||||
|
.patternOpacity = paper.patternOpacity(),
|
||||||
|
.gradientRotation = paper.gradientRotation(),
|
||||||
|
.isPattern = paper.isPattern(),
|
||||||
|
.tile = background->tile(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
push();
|
||||||
|
Background()->updates(
|
||||||
|
) | rpl::start_with_next([=](const BackgroundUpdate &update) {
|
||||||
|
if (update.type == BackgroundUpdate::Type::New
|
||||||
|
|| update.type == BackgroundUpdate::Type::Changed) {
|
||||||
|
push();
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<TextWithEntities> AddQuoteTracker(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const TextWithEntities "e) {
|
||||||
|
struct State {
|
||||||
|
std::unique_ptr<Ui::ChatTheme> theme;
|
||||||
|
std::unique_ptr<Ui::ChatStyle> style;
|
||||||
|
std::unique_ptr<PreviewDelegate> delegate;
|
||||||
|
std::unique_ptr<Element> element;
|
||||||
|
rpl::variable<TextSelection> selection;
|
||||||
|
Ui::PeerUserpicView userpic;
|
||||||
|
QPoint position;
|
||||||
|
|
||||||
|
base::Timer trippleClickTimer;
|
||||||
|
TextSelectType selectType = TextSelectType::Letters;
|
||||||
|
uint16 symbol = 0;
|
||||||
|
bool afterSymbol = false;
|
||||||
|
bool textCursor = false;
|
||||||
|
bool selecting = false;
|
||||||
|
bool over = false;
|
||||||
|
uint16 selectionStartSymbol = 0;
|
||||||
|
bool selectionStartAfterSymbol = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto preview = box->addRow(
|
||||||
|
object_ptr<Ui::RpWidget>(box),
|
||||||
|
QMargins(0, 0, 0, st::settingsThemesTopSkip));
|
||||||
|
const auto state = preview->lifetime().make_state<State>();
|
||||||
|
state->theme = DefaultThemeOn(preview->lifetime());
|
||||||
|
|
||||||
|
state->style = std::make_unique<Ui::ChatStyle>();
|
||||||
|
state->style->apply(state->theme.get());
|
||||||
|
|
||||||
|
state->delegate = std::make_unique<PreviewDelegate>(
|
||||||
|
box,
|
||||||
|
state->style.get(),
|
||||||
|
[=] { preview->update(); });
|
||||||
|
|
||||||
|
state->element = item->createView(state->delegate.get());
|
||||||
|
state->element->initDimensions();
|
||||||
|
state->position = QPoint(0, st::msgMargin.bottom());
|
||||||
|
|
||||||
|
state->selection = state->element->selectionFromQuote(quote);
|
||||||
|
|
||||||
|
const auto session = &show->session();
|
||||||
|
session->data().viewRepaintRequest(
|
||||||
|
) | rpl::start_with_next([=](not_null<const Element*> view) {
|
||||||
|
if (view == state->element.get()) {
|
||||||
|
preview->update();
|
||||||
|
}
|
||||||
|
}, preview->lifetime());
|
||||||
|
|
||||||
|
state->selection.changes() | rpl::start_with_next([=] {
|
||||||
|
preview->update();
|
||||||
|
}, preview->lifetime());
|
||||||
|
|
||||||
|
const auto resolveNewSelection = [=] {
|
||||||
|
const auto make = [](uint16 symbol, bool afterSymbol) {
|
||||||
|
return uint16(symbol + (afterSymbol ? 1 : 0));
|
||||||
|
};
|
||||||
|
const auto first = make(state->symbol, state->afterSymbol);
|
||||||
|
const auto second = make(
|
||||||
|
state->selectionStartSymbol,
|
||||||
|
state->selectionStartAfterSymbol);
|
||||||
|
const auto result = (first <= second)
|
||||||
|
? TextSelection{ first, second }
|
||||||
|
: TextSelection{ second, first };
|
||||||
|
return state->element->adjustSelection(result, state->selectType);
|
||||||
|
};
|
||||||
|
const auto startSelection = [=](TextSelectType type) {
|
||||||
|
if (state->selecting && state->selectType >= type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->selecting = true;
|
||||||
|
state->selectType = type;
|
||||||
|
state->selectionStartSymbol = state->symbol;
|
||||||
|
state->selectionStartAfterSymbol = state->afterSymbol;
|
||||||
|
if (!state->textCursor) {
|
||||||
|
preview->setCursor(style::cur_text);
|
||||||
|
}
|
||||||
|
preview->update();
|
||||||
|
};
|
||||||
|
preview->setMouseTracking(true);
|
||||||
|
preview->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||||
|
const auto type = e->type();
|
||||||
|
const auto mouse = static_cast<QMouseEvent*>(e.get());
|
||||||
|
if (type == QEvent::MouseMove) {
|
||||||
|
auto resolved = state->element->textState(
|
||||||
|
mouse->pos() - state->position,
|
||||||
|
{ .flags = Ui::Text::StateRequest::Flag::LookupSymbol });
|
||||||
|
state->over = true;
|
||||||
|
const auto text = (resolved.cursor == CursorState::Text);
|
||||||
|
if (state->textCursor != text) {
|
||||||
|
state->textCursor = text;
|
||||||
|
preview->setCursor((text || state->selecting)
|
||||||
|
? style::cur_text
|
||||||
|
: style::cur_default);
|
||||||
|
}
|
||||||
|
if (state->symbol != resolved.symbol
|
||||||
|
|| state->afterSymbol != resolved.afterSymbol) {
|
||||||
|
state->symbol = resolved.symbol;
|
||||||
|
state->afterSymbol = resolved.afterSymbol;
|
||||||
|
if (state->selecting) {
|
||||||
|
preview->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == QEvent::Leave && state->over) {
|
||||||
|
state->over = false;
|
||||||
|
if (state->textCursor) {
|
||||||
|
state->textCursor = false;
|
||||||
|
if (!state->selecting) {
|
||||||
|
preview->setCursor(style::cur_default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == QEvent::MouseButtonDblClick && state->over) {
|
||||||
|
startSelection(TextSelectType::Words);
|
||||||
|
state->trippleClickTimer.callOnce(
|
||||||
|
QApplication::doubleClickInterval());
|
||||||
|
} else if (type == QEvent::MouseButtonPress && state->over) {
|
||||||
|
startSelection(state->trippleClickTimer.isActive()
|
||||||
|
? TextSelectType::Paragraphs
|
||||||
|
: TextSelectType::Letters);
|
||||||
|
} else if (type == QEvent::MouseButtonRelease && state->selecting) {
|
||||||
|
const auto result = resolveNewSelection();
|
||||||
|
state->selecting = false;
|
||||||
|
state->selectType = TextSelectType::Letters;
|
||||||
|
state->selection = result;
|
||||||
|
if (!state->textCursor) {
|
||||||
|
preview->setCursor(style::cur_default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, preview->lifetime());
|
||||||
|
|
||||||
|
preview->widthValue(
|
||||||
|
) | rpl::filter([=](int width) {
|
||||||
|
return width > st::msgMinWidth;
|
||||||
|
}) | rpl::start_with_next([=](int width) {
|
||||||
|
const auto height = state->element->resizeGetHeight(width)
|
||||||
|
+ state->position.y()
|
||||||
|
+ st::msgMargin.top();
|
||||||
|
preview->resize(width, height);
|
||||||
|
}, preview->lifetime());
|
||||||
|
|
||||||
|
preview->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||||
|
Window::SectionWidget::PaintBackground(
|
||||||
|
state->theme.get(),
|
||||||
|
preview,
|
||||||
|
preview->window()->height(),
|
||||||
|
0,
|
||||||
|
clip);
|
||||||
|
|
||||||
|
auto p = Painter(preview);
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.translate(state->position);
|
||||||
|
auto context = state->theme->preparePaintContext(
|
||||||
|
state->style.get(),
|
||||||
|
preview->rect(),
|
||||||
|
clip,
|
||||||
|
!box->window()->isActiveWindow());
|
||||||
|
context.outbg = state->element->hasOutLayout();
|
||||||
|
context.selection = state->selecting
|
||||||
|
? resolveNewSelection()
|
||||||
|
: state->selection.current();
|
||||||
|
state->element->draw(p, context);
|
||||||
|
if (state->element->displayFromPhoto()) {
|
||||||
|
auto userpicMinBottomSkip = st::historyPaddingBottom
|
||||||
|
+ st::msgMargin.bottom();
|
||||||
|
auto userpicBottom = preview->height()
|
||||||
|
- state->element->marginBottom()
|
||||||
|
- state->element->marginTop();
|
||||||
|
const auto userpicTop = userpicBottom - st::msgPhotoSize;
|
||||||
|
if (const auto from = item->displayFrom()) {
|
||||||
|
from->paintUserpicLeft(
|
||||||
|
p,
|
||||||
|
state->userpic,
|
||||||
|
st::historyPhotoLeft,
|
||||||
|
userpicTop,
|
||||||
|
preview->width(),
|
||||||
|
st::msgPhotoSize);
|
||||||
|
} else if (const auto info = item->hiddenSenderInfo()) {
|
||||||
|
if (info->customUserpic.empty()) {
|
||||||
|
info->emptyUserpic.paintCircle(
|
||||||
|
p,
|
||||||
|
st::historyPhotoLeft,
|
||||||
|
userpicTop,
|
||||||
|
preview->width(),
|
||||||
|
st::msgPhotoSize);
|
||||||
|
} else {
|
||||||
|
const auto valid = info->paintCustomUserpic(
|
||||||
|
p,
|
||||||
|
state->userpic,
|
||||||
|
st::historyPhotoLeft,
|
||||||
|
userpicTop,
|
||||||
|
preview->width(),
|
||||||
|
st::msgPhotoSize);
|
||||||
|
if (!valid) {
|
||||||
|
info->customUserpic.load(session, item->fullId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Unexpected("Corrupt forwarded information in message.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, preview->lifetime());
|
||||||
|
|
||||||
|
return state->selection.value(
|
||||||
|
) | rpl::map([=](TextSelection selection) {
|
||||||
|
return state->element->selectedQuote(selection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
PreviewDelegate::PreviewDelegate(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
not_null<Ui::ChatStyle*> st,
|
||||||
|
Fn<void()> update)
|
||||||
|
: _parent(parent)
|
||||||
|
, _pathGradient(MakePathShiftGradient(st, update)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreviewDelegate::elementAnimationsPaused() {
|
||||||
|
return _parent->window()->isActiveWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PreviewDelegate::elementPathShiftGradient()
|
||||||
|
-> not_null<Ui::PathShiftGradient*> {
|
||||||
|
return _pathGradient.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Context PreviewDelegate::elementContext() {
|
||||||
|
return Context::History;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void ShowReplyToChatBox(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
FullReplyTo reply,
|
||||||
|
Fn<void()> clearOldDraft) {
|
||||||
|
class Controller final : public ChooseRecipientBoxController {
|
||||||
|
public:
|
||||||
|
using Chosen = not_null<Data::Thread*>;
|
||||||
|
|
||||||
|
Controller(not_null<Main::Session*> session)
|
||||||
|
: ChooseRecipientBoxController(
|
||||||
|
session,
|
||||||
|
[=](Chosen thread) mutable { _singleChosen.fire_copy(thread); },
|
||||||
|
nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void rowClicked(not_null<PeerListRow*> row) override final {
|
||||||
|
ChooseRecipientBoxController::rowClicked(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<Chosen> singleChosen() const{
|
||||||
|
return _singleChosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool respectSavedMessagesChat() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void prepareViewHook() override {
|
||||||
|
delegate()->peerListSetTitle(rpl::single(u"Reply in..."_q));
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::event_stream<Chosen> _singleChosen;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
not_null<PeerListBox*> box;
|
||||||
|
not_null<Controller*> controller;
|
||||||
|
base::unique_qptr<Ui::PopupMenu> menu;
|
||||||
|
};
|
||||||
|
const auto session = &show->session();
|
||||||
|
const auto state = [&] {
|
||||||
|
auto controller = std::make_unique<Controller>(session);
|
||||||
|
const auto controllerRaw = controller.get();
|
||||||
|
auto box = Box<PeerListBox>(std::move(controller), nullptr);
|
||||||
|
const auto boxRaw = box.data();
|
||||||
|
show->show(std::move(box));
|
||||||
|
auto state = State{ boxRaw, controllerRaw };
|
||||||
|
return boxRaw->lifetime().make_state<State>(std::move(state));
|
||||||
|
}();
|
||||||
|
|
||||||
|
auto chosen = [=](not_null<Data::Thread*> thread) mutable {
|
||||||
|
const auto history = thread->owningHistory();
|
||||||
|
const auto topicRootId = thread->topicRootId();
|
||||||
|
const auto draft = history->localDraft(topicRootId);
|
||||||
|
const auto textWithTags = draft
|
||||||
|
? draft->textWithTags
|
||||||
|
: TextWithTags();
|
||||||
|
const auto cursor = draft ? draft->cursor : MessageCursor();
|
||||||
|
reply.topicRootId = topicRootId;
|
||||||
|
history->setLocalDraft(std::make_unique<Data::Draft>(
|
||||||
|
textWithTags,
|
||||||
|
reply,
|
||||||
|
cursor,
|
||||||
|
Data::WebPageDraft()));
|
||||||
|
history->clearLocalEditDraft(topicRootId);
|
||||||
|
history->session().changes().entryUpdated(
|
||||||
|
thread,
|
||||||
|
Data::EntryUpdate::Flag::LocalDraftSet);
|
||||||
|
|
||||||
|
if (clearOldDraft) {
|
||||||
|
crl::on_main(&history->session(), clearOldDraft);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
auto callback = [=, chosen = std::move(chosen)](
|
||||||
|
Controller::Chosen thread) mutable {
|
||||||
|
const auto weak = Ui::MakeWeak(state->box);
|
||||||
|
if (!chosen(thread)) {
|
||||||
|
return;
|
||||||
|
} else if (const auto strong = weak.data()) {
|
||||||
|
strong->closeBox();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state->controller->singleChosen(
|
||||||
|
) | rpl::start_with_next(std::move(callback), state->box->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditReplyOptions(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
FullReplyTo reply,
|
||||||
|
Fn<void(FullReplyTo)> done,
|
||||||
|
Fn<void()> highlight,
|
||||||
|
Fn<void()> clearOldDraft) {
|
||||||
|
const auto session = &show->session();
|
||||||
|
const auto item = session->data().message(reply.messageId);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||||
|
box->setWidth(st::boxWideWidth);
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
rpl::variable<TextWithEntities> quote;
|
||||||
|
};
|
||||||
|
const auto state = box->lifetime().make_state<State>();
|
||||||
|
state->quote = AddQuoteTracker(box, show, item, reply.quote);
|
||||||
|
|
||||||
|
box->setTitle(reply.quote.empty()
|
||||||
|
? rpl::single(u"Reply to Message"_q)
|
||||||
|
: rpl::single(u"Update Quote"_q));
|
||||||
|
|
||||||
|
Settings::AddButton(
|
||||||
|
box->verticalLayout(),
|
||||||
|
rpl::single(u"Reply in another chat"_q),
|
||||||
|
st::settingsButton,
|
||||||
|
{ &st::menuIconReply }
|
||||||
|
)->setClickedCallback([=] {
|
||||||
|
ShowReplyToChatBox(show, reply, clearOldDraft);
|
||||||
|
});
|
||||||
|
|
||||||
|
Settings::AddButton(
|
||||||
|
box->verticalLayout(),
|
||||||
|
rpl::single(u"Show message"_q),
|
||||||
|
st::settingsButton,
|
||||||
|
{ &st::menuIconShowInChat }
|
||||||
|
)->setClickedCallback(highlight);
|
||||||
|
|
||||||
|
const auto finish = [=](FullReplyTo result) {
|
||||||
|
const auto weak = Ui::MakeWeak(box);
|
||||||
|
done(std::move(result));
|
||||||
|
if (const auto strong = weak.data()) {
|
||||||
|
strong->closeBox();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Settings::AddButton(
|
||||||
|
box->verticalLayout(),
|
||||||
|
rpl::single(u"Remove reply"_q),
|
||||||
|
st::settingsAttentionButtonWithIcon,
|
||||||
|
{ &st::menuIconDeleteAttention }
|
||||||
|
)->setClickedCallback([=] {
|
||||||
|
finish({});
|
||||||
|
});
|
||||||
|
|
||||||
|
box->addButton(rpl::single(u"Apply"_q), [=] {
|
||||||
|
auto result = reply;
|
||||||
|
result.quote = state->quote.current();
|
||||||
|
finish(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
box->addButton(tr::lng_cancel(), [=] {
|
||||||
|
box->closeBox();
|
||||||
|
});
|
||||||
|
|
||||||
|
session->data().itemRemoved(
|
||||||
|
) | rpl::filter([=](not_null<const HistoryItem*> removed) {
|
||||||
|
return removed == item;
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
finish({});
|
||||||
|
}, box->lifetime());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HistoryView::Controls
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
namespace ChatHelpers {
|
||||||
|
class Show;
|
||||||
|
} // namespace ChatHelpers
|
||||||
|
|
||||||
|
namespace Window {
|
||||||
|
class SessionController;
|
||||||
|
} // namespace Window
|
||||||
|
|
||||||
|
namespace HistoryView::Controls {
|
||||||
|
|
||||||
|
void EditReplyOptions(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
FullReplyTo reply,
|
||||||
|
Fn<void(FullReplyTo)> done,
|
||||||
|
Fn<void()> highlight,
|
||||||
|
Fn<void()> clearOldDraft);
|
||||||
|
|
||||||
|
void ShowReplyToChatBox(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
FullReplyTo reply,
|
||||||
|
Fn<void()> clearOldDraft = nullptr);
|
||||||
|
|
||||||
|
} // namespace HistoryView::Controls
|
|
@ -395,6 +395,11 @@ public:
|
||||||
virtual TextWithEntities selectedQuote(
|
virtual TextWithEntities selectedQuote(
|
||||||
const Ui::Text::String &text,
|
const Ui::Text::String &text,
|
||||||
TextSelection selection) const = 0;
|
TextSelection selection) const = 0;
|
||||||
|
virtual TextSelection selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const = 0;
|
||||||
|
virtual TextSelection selectionFromQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
const TextWithEntities "e) const = 0;
|
||||||
[[nodiscard]] virtual TextSelection adjustSelection(
|
[[nodiscard]] virtual TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const;
|
TextSelectType type) const;
|
||||||
|
|
|
@ -56,7 +56,7 @@ namespace {
|
||||||
constexpr auto kPlayStatusLimit = 2;
|
constexpr auto kPlayStatusLimit = 2;
|
||||||
const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
|
const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
|
||||||
|
|
||||||
std::optional<Window::SessionController*> ExtractController(
|
[[nodiscard]] std::optional<Window::SessionController*> ExtractController(
|
||||||
const ClickContext &context) {
|
const ClickContext &context) {
|
||||||
const auto my = context.other.value<ClickHandlerContext>();
|
const auto my = context.other.value<ClickHandlerContext>();
|
||||||
if (const auto controller = my.sessionWindow.get()) {
|
if (const auto controller = my.sessionWindow.get()) {
|
||||||
|
@ -65,6 +65,42 @@ std::optional<Window::SessionController*> ExtractController(
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool CheckQuoteEntities(
|
||||||
|
const EntitiesInText "eEntities,
|
||||||
|
const TextWithEntities &original,
|
||||||
|
TextSelection selection) {
|
||||||
|
auto left = quoteEntities;
|
||||||
|
const auto allowed = std::array{
|
||||||
|
EntityType::Bold,
|
||||||
|
EntityType::Italic,
|
||||||
|
EntityType::Underline,
|
||||||
|
EntityType::StrikeOut,
|
||||||
|
EntityType::Spoiler,
|
||||||
|
EntityType::CustomEmoji,
|
||||||
|
};
|
||||||
|
for (const auto &entity : original.entities) {
|
||||||
|
const auto from = entity.offset();
|
||||||
|
const auto till = from + entity.length();
|
||||||
|
if (till <= selection.from || from >= selection.to) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto quoteFrom = std::max(from, int(selection.from));
|
||||||
|
const auto quoteTill = std::min(till, int(selection.to));
|
||||||
|
const auto cut = EntityInText(
|
||||||
|
entity.type(),
|
||||||
|
quoteFrom - int(selection.from),
|
||||||
|
quoteTill - quoteFrom,
|
||||||
|
entity.data());
|
||||||
|
const auto i = ranges::find(left, cut);
|
||||||
|
if (i != left.end()) {
|
||||||
|
left.erase(i);
|
||||||
|
} else if (!ranges::contains(allowed, cut.type())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left.empty();
|
||||||
|
};
|
||||||
|
|
||||||
class KeyboardStyle : public ReplyKeyboard::Style {
|
class KeyboardStyle : public ReplyKeyboard::Style {
|
||||||
public:
|
public:
|
||||||
KeyboardStyle(const style::BotKeyboardButton &st);
|
KeyboardStyle(const style::BotKeyboardButton &st);
|
||||||
|
@ -2674,6 +2710,66 @@ TextWithEntities Message::selectedQuote(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextSelection Message::selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
const auto item = data();
|
||||||
|
const auto &translated = item->translatedText();
|
||||||
|
const auto &original = item->originalText();
|
||||||
|
if (&translated != &original || quote.empty()) {
|
||||||
|
return {};
|
||||||
|
} else if (hasVisibleText()) {
|
||||||
|
return selectionFromQuote(text(), quote);
|
||||||
|
} else if (const auto media = this->media()) {
|
||||||
|
if (media->isDisplayed() || isHiddenByGroup()) {
|
||||||
|
return media->selectionFromQuote(quote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TextSelection Message::selectionFromQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
if (quote.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto &original = data()->originalText();
|
||||||
|
auto result = TextSelection();
|
||||||
|
auto offset = 0;
|
||||||
|
while (true) {
|
||||||
|
const auto i = original.text.indexOf(quote.text, offset);
|
||||||
|
if (i < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto selection = TextSelection{
|
||||||
|
uint16(i),
|
||||||
|
uint16(i + quote.text.size()),
|
||||||
|
};
|
||||||
|
if (CheckQuoteEntities(quote.entities, original, selection)) {
|
||||||
|
result = selection;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset = i + 1;
|
||||||
|
}
|
||||||
|
const auto &modifications = text.modifications();
|
||||||
|
//for (const auto &modification : text.modifications()) {
|
||||||
|
// if (modification.position >= selection.to) {
|
||||||
|
// break;
|
||||||
|
// } else if (modification.position <= selection.from) {
|
||||||
|
// modified.from += modification.skipped;
|
||||||
|
// if (modification.added
|
||||||
|
// && modification.position < selection.from) {
|
||||||
|
// --modified.from;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// modified.to += modification.skipped;
|
||||||
|
// if (modification.added && modified.to > modified.from) {
|
||||||
|
// --modified.to;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
TextSelection Message::adjustSelection(
|
TextSelection Message::adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const {
|
TextSelectType type) const {
|
||||||
|
|
|
@ -99,6 +99,11 @@ public:
|
||||||
TextWithEntities selectedQuote(
|
TextWithEntities selectedQuote(
|
||||||
const Ui::Text::String &text,
|
const Ui::Text::String &text,
|
||||||
TextSelection selection) const override;
|
TextSelection selection) const override;
|
||||||
|
TextSelection selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const override;
|
||||||
|
TextSelection selectionFromQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
const TextWithEntities "e) const override;
|
||||||
TextSelection adjustSelection(
|
TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const override;
|
TextSelectType type) const override;
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "history/view/controls/history_view_compose_controls.h"
|
#include "history/view/controls/history_view_compose_controls.h"
|
||||||
#include "history/view/controls/history_view_forward_panel.h"
|
#include "history/view/controls/history_view_forward_panel.h"
|
||||||
|
#include "history/view/controls/history_view_reply_options.h"
|
||||||
#include "history/view/history_view_top_bar_widget.h"
|
#include "history/view/history_view_top_bar_widget.h"
|
||||||
#include "history/view/history_view_list_widget.h"
|
#include "history/view/history_view_list_widget.h"
|
||||||
#include "history/view/history_view_schedule_box.h"
|
#include "history/view/history_view_schedule_box.h"
|
||||||
|
|
|
@ -679,6 +679,17 @@ TextWithEntities Service::selectedQuote(
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextSelection Service::selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TextSelection Service::selectionFromQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
TextSelection Service::adjustSelection(
|
TextSelection Service::adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const {
|
TextSelectType type) const {
|
||||||
|
|
|
@ -47,6 +47,11 @@ public:
|
||||||
TextWithEntities selectedQuote(
|
TextWithEntities selectedQuote(
|
||||||
const Ui::Text::String &text,
|
const Ui::Text::String &text,
|
||||||
TextSelection selection) const override;
|
TextSelection selection) const override;
|
||||||
|
TextSelection selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const override;
|
||||||
|
TextSelection selectionFromQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
const TextWithEntities "e) const override;
|
||||||
TextSelection adjustSelection(
|
TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const override;
|
TextSelectType type) const override;
|
||||||
|
|
|
@ -1226,6 +1226,24 @@ TextWithEntities Document::selectedQuote(TextSelection selection) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextSelection Document::selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
|
const auto result = parent()->selectionFromQuote(
|
||||||
|
captioned->caption,
|
||||||
|
quote);
|
||||||
|
if (result.empty()) {
|
||||||
|
return {};
|
||||||
|
} else if (const auto voice = Get<HistoryDocumentVoice>()) {
|
||||||
|
return HistoryView::ShiftItemSelection(
|
||||||
|
result,
|
||||||
|
voice->transcribeText);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool Document::uploading() const {
|
bool Document::uploading() const {
|
||||||
return _data->uploading();
|
return _data->uploading();
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ public:
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
TextWithEntities selectedQuote(TextSelection selection) const override;
|
||||||
|
TextSelection selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
bool uploading() const override;
|
bool uploading() const override;
|
||||||
|
|
||||||
|
|
|
@ -1207,6 +1207,11 @@ TextWithEntities Gif::selectedQuote(TextSelection selection) const {
|
||||||
return parent()->selectedQuote(_caption, selection);
|
return parent()->selectedQuote(_caption, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextSelection Gif::selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
return parent()->selectionFromQuote(_caption, quote);
|
||||||
|
}
|
||||||
|
|
||||||
bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
||||||
return (sides & RectPart::Left) && (sides & RectPart::Right);
|
return (sides & RectPart::Left) && (sides & RectPart::Right);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,8 @@ public:
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
TextWithEntities selectedQuote(TextSelection selection) const override;
|
||||||
|
TextSelection selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
bool uploading() const override;
|
bool uploading() const override;
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,10 @@ public:
|
||||||
TextSelection selection) const {
|
TextSelection selection) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] virtual TextSelection selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] virtual bool isDisplayed() const;
|
[[nodiscard]] virtual bool isDisplayed() const;
|
||||||
virtual void updateNeedBubbleState() {
|
virtual void updateNeedBubbleState() {
|
||||||
|
|
|
@ -1053,6 +1053,11 @@ TextWithEntities Photo::selectedQuote(TextSelection selection) const {
|
||||||
return parent()->selectedQuote(_caption, selection);
|
return parent()->selectedQuote(_caption, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextSelection Photo::selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
return parent()->selectionFromQuote(_caption, quote);
|
||||||
|
}
|
||||||
|
|
||||||
void Photo::hideSpoilers() {
|
void Photo::hideSpoilers() {
|
||||||
_caption.setSpoilerRevealed(false, anim::type::instant);
|
_caption.setSpoilerRevealed(false, anim::type::instant);
|
||||||
if (_spoiler) {
|
if (_spoiler) {
|
||||||
|
|
|
@ -58,6 +58,8 @@ public:
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
TextWithEntities selectedQuote(TextSelection selection) const override;
|
||||||
|
TextSelection selectionFromQuote(
|
||||||
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
PhotoData *getPhoto() const override {
|
PhotoData *getPhoto() const override {
|
||||||
return _data;
|
return _data;
|
||||||
|
|
Loading…
Add table
Reference in a new issue