mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Extract FieldAutocomplete code.
This commit is contained in:
parent
969152e949
commit
ba7cd25f21
10 changed files with 497 additions and 481 deletions
|
@ -10,20 +10,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace ChatHelpers {
|
namespace ChatHelpers {
|
||||||
|
|
||||||
struct ComposeFeatures {
|
struct ComposeFeatures {
|
||||||
bool likes = false;
|
bool likes : 1 = false;
|
||||||
bool sendAs = true;
|
bool sendAs : 1 = true;
|
||||||
bool ttlInfo = true;
|
bool ttlInfo : 1 = true;
|
||||||
bool botCommandSend = true;
|
bool botCommandSend : 1 = true;
|
||||||
bool silentBroadcastToggle = true;
|
bool silentBroadcastToggle : 1 = true;
|
||||||
bool attachBotsMenu = true;
|
bool attachBotsMenu : 1 = true;
|
||||||
bool inlineBots = true;
|
bool inlineBots : 1 = true;
|
||||||
bool megagroupSet = true;
|
bool megagroupSet : 1 = true;
|
||||||
bool stickersSettings = true;
|
bool stickersSettings : 1 = true;
|
||||||
bool openStickerSets = true;
|
bool openStickerSets : 1 = true;
|
||||||
bool autocompleteHashtags = true;
|
bool autocompleteHashtags : 1 = true;
|
||||||
bool autocompleteMentions = true;
|
bool autocompleteMentions : 1 = true;
|
||||||
bool autocompleteCommands = true;
|
bool autocompleteCommands : 1 = true;
|
||||||
bool commonTabbedPanel = true;
|
bool suggestStickersByEmoji : 1 = true;
|
||||||
|
bool commonTabbedPanel : 1 = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ChatHelpers
|
} // namespace ChatHelpers
|
||||||
|
|
|
@ -713,11 +713,13 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SuggestionsController::SuggestionsController(
|
SuggestionsController::SuggestionsController(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
not_null<QTextEdit*> field,
|
not_null<QTextEdit*> field,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const Options &options)
|
const Options &options)
|
||||||
: _st(options.st ? *options.st : st::defaultEmojiSuggestions)
|
: QObject(parent)
|
||||||
|
, _st(options.st ? *options.st : st::defaultEmojiSuggestions)
|
||||||
, _field(field)
|
, _field(field)
|
||||||
, _session(session)
|
, _session(session)
|
||||||
, _showExactTimer([=] { showWithQuery(getEmojiQuery()); })
|
, _showExactTimer([=] { showWithQuery(getEmojiQuery()); })
|
||||||
|
|
|
@ -37,7 +37,7 @@ class SuggestionsWidget;
|
||||||
|
|
||||||
using SuggestionsQuery = std::variant<QString, EmojiPtr>;
|
using SuggestionsQuery = std::variant<QString, EmojiPtr>;
|
||||||
|
|
||||||
class SuggestionsController {
|
class SuggestionsController final : public QObject {
|
||||||
public:
|
public:
|
||||||
struct Options {
|
struct Options {
|
||||||
bool suggestExactFirstWord = true;
|
bool suggestExactFirstWord = true;
|
||||||
|
@ -47,6 +47,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
SuggestionsController(
|
SuggestionsController(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
not_null<QTextEdit*> field,
|
not_null<QTextEdit*> field,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/business/data_shortcut_messages.h"
|
#include "data/business/data_shortcut_messages.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -202,12 +203,6 @@ struct FieldAutocomplete::BotCommandRow {
|
||||||
Ui::Text::String descriptionText;
|
Ui::Text::String descriptionText;
|
||||||
};
|
};
|
||||||
|
|
||||||
FieldAutocomplete::FieldAutocomplete(
|
|
||||||
QWidget *parent,
|
|
||||||
not_null<Window::SessionController*> controller)
|
|
||||||
: FieldAutocomplete(parent, controller->uiShow()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
FieldAutocomplete::FieldAutocomplete(
|
FieldAutocomplete::FieldAutocomplete(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
std::shared_ptr<Show> show,
|
std::shared_ptr<Show> show,
|
||||||
|
@ -252,6 +247,22 @@ std::shared_ptr<Show> FieldAutocomplete::uiShow() const {
|
||||||
return _show;
|
return _show;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::requestRefresh() {
|
||||||
|
_refreshRequests.fire({});
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> FieldAutocomplete::refreshRequests() const {
|
||||||
|
return _refreshRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FieldAutocomplete::requestStickersUpdate() {
|
||||||
|
_stickersUpdateRequests.fire({});
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> FieldAutocomplete::stickersUpdateRequests() const {
|
||||||
|
return _stickersUpdateRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
auto FieldAutocomplete::mentionChosen() const
|
auto FieldAutocomplete::mentionChosen() const
|
||||||
-> rpl::producer<FieldAutocomplete::MentionChosen> {
|
-> rpl::producer<FieldAutocomplete::MentionChosen> {
|
||||||
return _inner->mentionChosen();
|
return _inner->mentionChosen();
|
||||||
|
@ -378,6 +389,10 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
|
||||||
updateFiltered(resetScroll);
|
updateFiltered(resetScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EmojiPtr FieldAutocomplete::stickersEmoji() const {
|
||||||
|
return _emoji;
|
||||||
|
}
|
||||||
|
|
||||||
bool FieldAutocomplete::clearFilteredBotCommands() {
|
bool FieldAutocomplete::clearFilteredBotCommands() {
|
||||||
if (_brows.empty()) {
|
if (_brows.empty()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1634,4 +1649,165 @@ auto FieldAutocomplete::Inner::scrollToRequested() const
|
||||||
return _scrollToRequested.events();
|
return _scrollToRequested.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InitFieldAutocomplete(
|
||||||
|
std::unique_ptr<FieldAutocomplete> &autocomplete,
|
||||||
|
FieldAutocompleteDescriptor &&descriptor) {
|
||||||
|
Expects(!autocomplete);
|
||||||
|
|
||||||
|
autocomplete = std::make_unique<FieldAutocomplete>(
|
||||||
|
descriptor.parent,
|
||||||
|
descriptor.show,
|
||||||
|
descriptor.stOverride);
|
||||||
|
const auto raw = autocomplete.get();
|
||||||
|
const auto field = descriptor.field;
|
||||||
|
|
||||||
|
field->rawTextEdit()->installEventFilter(raw);
|
||||||
|
|
||||||
|
raw->mentionChosen(
|
||||||
|
) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
|
||||||
|
const auto user = data.user;
|
||||||
|
if (data.mention.isEmpty()) {
|
||||||
|
field->insertTag(
|
||||||
|
user->firstName.isEmpty() ? user->name() : user->firstName,
|
||||||
|
PrepareMentionTag(user));
|
||||||
|
} else {
|
||||||
|
field->insertTag('@' + data.mention);
|
||||||
|
}
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
const auto sendCommand = descriptor.sendBotCommand;
|
||||||
|
const auto setText = descriptor.setText;
|
||||||
|
|
||||||
|
raw->hashtagChosen(
|
||||||
|
) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) {
|
||||||
|
field->insertTag(data.hashtag);
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
const auto peer = descriptor.peer;
|
||||||
|
const auto processShortcut = descriptor.processShortcut;
|
||||||
|
const auto shortcutMessages = (processShortcut != nullptr)
|
||||||
|
? &peer->owner().shortcutMessages()
|
||||||
|
: nullptr;
|
||||||
|
raw->botCommandChosen(
|
||||||
|
) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) {
|
||||||
|
using Method = FieldAutocompleteChooseMethod;
|
||||||
|
const auto byTab = (data.method == Method::ByTab);
|
||||||
|
const auto shortcut = data.user->isSelf();
|
||||||
|
|
||||||
|
// Send bot command at once, if it was not inserted by pressing Tab.
|
||||||
|
if (byTab && data.command.size() > 1) {
|
||||||
|
field->insertTag(data.command);
|
||||||
|
} else if (!shortcut) {
|
||||||
|
sendCommand(data.command);
|
||||||
|
setText(
|
||||||
|
field->getTextWithTagsPart(field->textCursor().position()));
|
||||||
|
} else if (processShortcut) {
|
||||||
|
processShortcut(data.command.mid(1));
|
||||||
|
}
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
raw->setModerateKeyActivateCallback(std::move(descriptor.moderateKeyActivateCallback));
|
||||||
|
|
||||||
|
const auto stickerChoosing = descriptor.stickerChoosing;
|
||||||
|
raw->choosingProcesses(
|
||||||
|
) | rpl::start_with_next([=](FieldAutocomplete::Type type) {
|
||||||
|
if (type == FieldAutocomplete::Type::Stickers) {
|
||||||
|
stickerChoosing();
|
||||||
|
}
|
||||||
|
}, raw->lifetime());
|
||||||
|
if (auto chosen = descriptor.stickerChosen) {
|
||||||
|
raw->stickerChosen(
|
||||||
|
) | rpl::start_with_next(std::move(chosen), raw->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
field->tabbed(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
if (!raw->isHidden()) {
|
||||||
|
raw->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
|
||||||
|
}
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
const auto features = descriptor.features;
|
||||||
|
const auto check = [=] {
|
||||||
|
auto parsed = ParseMentionHashtagBotCommandQuery(field, features());
|
||||||
|
if (parsed.query.isEmpty()) {
|
||||||
|
} else if (parsed.query[0] == '#'
|
||||||
|
&& cRecentWriteHashtags().isEmpty()
|
||||||
|
&& cRecentSearchHashtags().isEmpty()) {
|
||||||
|
peer->session().local().readRecentHashtagsAndBots();
|
||||||
|
} else if (parsed.query[0] == '@'
|
||||||
|
&& cRecentInlineBots().isEmpty()) {
|
||||||
|
peer->session().local().readRecentHashtagsAndBots();
|
||||||
|
} else if (parsed.query[0] == '/'
|
||||||
|
&& peer->isUser()
|
||||||
|
&& !peer->asUser()->isBot()
|
||||||
|
&& (!shortcutMessages
|
||||||
|
|| shortcutMessages->shortcuts().list.empty())) {
|
||||||
|
parsed = {};
|
||||||
|
}
|
||||||
|
raw->showFiltered(peer, parsed.query, parsed.fromStart);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto updateStickersByEmoji = [=] {
|
||||||
|
const auto errorForStickers = Data::RestrictionError(
|
||||||
|
peer,
|
||||||
|
ChatRestriction::SendStickers);
|
||||||
|
if (features().suggestStickersByEmoji && !errorForStickers) {
|
||||||
|
const auto &text = field->getTextWithTags().text;
|
||||||
|
auto length = 0;
|
||||||
|
if (const auto emoji = Ui::Emoji::Find(text, &length)) {
|
||||||
|
if (text.size() <= length) {
|
||||||
|
raw->showStickers(emoji);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raw->showStickers(nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
raw->refreshRequests(
|
||||||
|
) | rpl::start_with_next(check, raw->lifetime());
|
||||||
|
|
||||||
|
raw->stickersUpdateRequests(
|
||||||
|
) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||||
|
|
||||||
|
peer->owner().botCommandsChanges(
|
||||||
|
) | rpl::filter([=](not_null<PeerData*> changed) {
|
||||||
|
return (peer == changed);
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
if (raw->clearFilteredBotCommands()) {
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
peer->owner().stickers().updated(
|
||||||
|
Data::StickersType::Stickers
|
||||||
|
) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
field->rawTextEdit(),
|
||||||
|
&QTextEdit::cursorPositionChanged,
|
||||||
|
raw,
|
||||||
|
check,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
|
field->changes() | rpl::start_with_next(
|
||||||
|
updateStickersByEmoji,
|
||||||
|
raw->lifetime());
|
||||||
|
|
||||||
|
peer->session().changes().peerUpdates(
|
||||||
|
Data::PeerUpdate::Flag::Rights
|
||||||
|
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||||
|
return (update.peer == peer);
|
||||||
|
}) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||||
|
|
||||||
|
if (shortcutMessages) {
|
||||||
|
shortcutMessages->shortcutsChanged(
|
||||||
|
) | rpl::start_with_next(check, raw->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
raw->setSendMenuDetails(std::move(descriptor.sendMenuDetails));
|
||||||
|
raw->hideFast();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ChatHelpers
|
} // namespace ChatHelpers
|
||||||
|
|
|
@ -47,6 +47,7 @@ struct Details;
|
||||||
|
|
||||||
namespace ChatHelpers {
|
namespace ChatHelpers {
|
||||||
|
|
||||||
|
struct ComposeFeatures;
|
||||||
struct FileChosen;
|
struct FileChosen;
|
||||||
class Show;
|
class Show;
|
||||||
|
|
||||||
|
@ -58,9 +59,6 @@ enum class FieldAutocompleteChooseMethod {
|
||||||
|
|
||||||
class FieldAutocomplete final : public Ui::RpWidget {
|
class FieldAutocomplete final : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
FieldAutocomplete(
|
|
||||||
QWidget *parent,
|
|
||||||
not_null<Window::SessionController*> controller);
|
|
||||||
FieldAutocomplete(
|
FieldAutocomplete(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
std::shared_ptr<Show> show,
|
std::shared_ptr<Show> show,
|
||||||
|
@ -74,16 +72,19 @@ public:
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
QString query,
|
QString query,
|
||||||
bool addInlineBots);
|
bool addInlineBots);
|
||||||
|
|
||||||
void showStickers(EmojiPtr emoji);
|
void showStickers(EmojiPtr emoji);
|
||||||
|
[[nodiscard]] EmojiPtr stickersEmoji() const;
|
||||||
|
|
||||||
void setBoundings(QRect boundings);
|
void setBoundings(QRect boundings);
|
||||||
|
|
||||||
const QString &filter() const;
|
[[nodiscard]] const QString &filter() const;
|
||||||
ChatData *chat() const;
|
[[nodiscard]] ChatData *chat() const;
|
||||||
ChannelData *channel() const;
|
[[nodiscard]] ChannelData *channel() const;
|
||||||
UserData *user() const;
|
[[nodiscard]] UserData *user() const;
|
||||||
|
|
||||||
int32 innerTop();
|
[[nodiscard]] int32 innerTop();
|
||||||
int32 innerBottom();
|
[[nodiscard]] int32 innerBottom();
|
||||||
|
|
||||||
bool eventFilter(QObject *obj, QEvent *e) override;
|
bool eventFilter(QObject *obj, QEvent *e) override;
|
||||||
|
|
||||||
|
@ -112,13 +113,14 @@ public:
|
||||||
|
|
||||||
bool chooseSelected(ChooseMethod method) const;
|
bool chooseSelected(ChooseMethod method) const;
|
||||||
|
|
||||||
bool stickersShown() const {
|
[[nodiscard]] bool stickersShown() const {
|
||||||
return !_srows.empty();
|
return !_srows.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool overlaps(const QRect &globalRect) {
|
[[nodiscard]] bool overlaps(const QRect &globalRect) {
|
||||||
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
|
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
|
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,11 +133,16 @@ public:
|
||||||
void showAnimated();
|
void showAnimated();
|
||||||
void hideAnimated();
|
void hideAnimated();
|
||||||
|
|
||||||
rpl::producer<MentionChosen> mentionChosen() const;
|
void requestRefresh();
|
||||||
rpl::producer<HashtagChosen> hashtagChosen() const;
|
[[nodiscard]] rpl::producer<> refreshRequests() const;
|
||||||
rpl::producer<BotCommandChosen> botCommandChosen() const;
|
void requestStickersUpdate();
|
||||||
rpl::producer<StickerChosen> stickerChosen() const;
|
[[nodiscard]] rpl::producer<> stickersUpdateRequests() const;
|
||||||
rpl::producer<Type> choosingProcesses() const;
|
|
||||||
|
[[nodiscard]] rpl::producer<MentionChosen> mentionChosen() const;
|
||||||
|
[[nodiscard]] rpl::producer<HashtagChosen> hashtagChosen() const;
|
||||||
|
[[nodiscard]] rpl::producer<BotCommandChosen> botCommandChosen() const;
|
||||||
|
[[nodiscard]] rpl::producer<StickerChosen> stickerChosen() const;
|
||||||
|
[[nodiscard]] rpl::producer<Type> choosingProcesses() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
@ -191,9 +198,30 @@ private:
|
||||||
bool _hiding = false;
|
bool _hiding = false;
|
||||||
|
|
||||||
Ui::Animations::Simple _a_opacity;
|
Ui::Animations::Simple _a_opacity;
|
||||||
|
rpl::event_stream<> _refreshRequests;
|
||||||
|
rpl::event_stream<> _stickersUpdateRequests;
|
||||||
|
|
||||||
Fn<bool(int)> _moderateKeyActivateCallback;
|
Fn<bool(int)> _moderateKeyActivateCallback;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FieldAutocompleteDescriptor {
|
||||||
|
not_null<QWidget*> parent;
|
||||||
|
std::shared_ptr<Show> show;
|
||||||
|
not_null<Ui::InputField*> field;
|
||||||
|
const style::EmojiPan *stOverride = nullptr;
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
Fn<ComposeFeatures()> features;
|
||||||
|
Fn<SendMenu::Details()> sendMenuDetails;
|
||||||
|
Fn<void()> stickerChoosing;
|
||||||
|
Fn<void(FileChosen&&)> stickerChosen;
|
||||||
|
Fn<void(TextWithTags)> setText;
|
||||||
|
Fn<void(QString)> sendBotCommand;
|
||||||
|
Fn<void(QString)> processShortcut;
|
||||||
|
Fn<bool(int)> moderateKeyActivateCallback;
|
||||||
|
};
|
||||||
|
void InitFieldAutocomplete(
|
||||||
|
std::unique_ptr<FieldAutocomplete> &autocomplete,
|
||||||
|
FieldAutocompleteDescriptor &&descriptor);
|
||||||
|
|
||||||
} // namespace ChatHelpers
|
} // namespace ChatHelpers
|
||||||
|
|
|
@ -243,7 +243,6 @@ HistoryWidget::HistoryWidget(
|
||||||
_scroll.data(),
|
_scroll.data(),
|
||||||
controller->chatStyle(),
|
controller->chatStyle(),
|
||||||
static_cast<HistoryView::CornerButtonsDelegate*>(this))
|
static_cast<HistoryView::CornerButtonsDelegate*>(this))
|
||||||
, _fieldAutocomplete(this, controller->uiShow())
|
|
||||||
, _supportAutocomplete(session().supportMode()
|
, _supportAutocomplete(session().supportMode()
|
||||||
? object_ptr<Support::Autocomplete>(this, &session())
|
? object_ptr<Support::Autocomplete>(this, &session())
|
||||||
: nullptr)
|
: nullptr)
|
||||||
|
@ -416,15 +415,6 @@ HistoryWidget::HistoryWidget(
|
||||||
saveDraftDelayed();
|
saveDraftDelayed();
|
||||||
}, _field->lifetime());
|
}, _field->lifetime());
|
||||||
|
|
||||||
connect(rawTextEdit, &QTextEdit::cursorPositionChanged, this, [=] {
|
|
||||||
checkFieldAutocomplete();
|
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
|
|
||||||
controller->session().data().shortcutMessages().shortcutsChanged(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
checkFieldAutocomplete();
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
_fieldBarCancel->hide();
|
_fieldBarCancel->hide();
|
||||||
|
|
||||||
_topBar->hide();
|
_topBar->hide();
|
||||||
|
@ -473,85 +463,10 @@ HistoryWidget::HistoryWidget(
|
||||||
sendBotCommand(r);
|
sendBotCommand(r);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_fieldAutocomplete->mentionChosen(
|
|
||||||
) | rpl::start_with_next([=](
|
|
||||||
ChatHelpers::FieldAutocomplete::MentionChosen data) {
|
|
||||||
auto replacement = QString();
|
|
||||||
auto entityTag = QString();
|
|
||||||
if (data.mention.isEmpty()) {
|
|
||||||
replacement = data.user->firstName;
|
|
||||||
if (replacement.isEmpty()) {
|
|
||||||
replacement = data.user->name();
|
|
||||||
}
|
|
||||||
entityTag = PrepareMentionTag(data.user);
|
|
||||||
} else {
|
|
||||||
replacement = '@' + data.mention;
|
|
||||||
}
|
|
||||||
_field->insertTag(replacement, entityTag);
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
_fieldAutocomplete->hashtagChosen(
|
|
||||||
) | rpl::start_with_next([=](
|
|
||||||
ChatHelpers::FieldAutocomplete::HashtagChosen data) {
|
|
||||||
insertHashtagOrBotCommand(data.hashtag, data.method);
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
_fieldAutocomplete->botCommandChosen(
|
|
||||||
) | rpl::start_with_next([=](
|
|
||||||
ChatHelpers::FieldAutocomplete::BotCommandChosen data) {
|
|
||||||
using Method = ChatHelpers::FieldAutocompleteChooseMethod;
|
|
||||||
const auto messages = &data.user->owner().shortcutMessages();
|
|
||||||
const auto shortcut = data.user->isSelf();
|
|
||||||
const auto command = data.command.mid(1);
|
|
||||||
const auto byTab = (data.method == Method::ByTab);
|
|
||||||
const auto shortcutId = (_peer && shortcut && !byTab)
|
|
||||||
? messages->lookupShortcutId(command)
|
|
||||||
: BusinessShortcutId();
|
|
||||||
if (shortcut && command.isEmpty()) {
|
|
||||||
controller->showSettings(Settings::QuickRepliesId());
|
|
||||||
} else if (!shortcutId) {
|
|
||||||
insertHashtagOrBotCommand(data.command, data.method);
|
|
||||||
} else if (!_peer->session().premium()) {
|
|
||||||
ShowPremiumPreviewToBuy(
|
|
||||||
controller,
|
|
||||||
PremiumFeature::QuickReplies);
|
|
||||||
} else {
|
|
||||||
session().api().sendShortcutMessages(_peer, shortcutId);
|
|
||||||
session().api().finishForwarding(prepareSendAction({}));
|
|
||||||
setFieldText(_field->getTextWithTagsPart(_field->textCursor().position()));
|
|
||||||
}
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
_fieldAutocomplete->setModerateKeyActivateCallback([=](int key) {
|
|
||||||
const auto context = [=](FullMsgId itemId) {
|
|
||||||
return _list->prepareClickContext(Qt::LeftButton, itemId);
|
|
||||||
};
|
|
||||||
return !_keyboard->isHidden() && _keyboard->moderateKeyActivate(
|
|
||||||
key,
|
|
||||||
context);
|
|
||||||
});
|
|
||||||
|
|
||||||
_fieldAutocomplete->choosingProcesses(
|
|
||||||
) | rpl::start_with_next([=](ChatHelpers::FieldAutocomplete::Type type) {
|
|
||||||
if (!_history) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type == ChatHelpers::FieldAutocomplete::Type::Stickers) {
|
|
||||||
session().sendProgressManager().update(
|
|
||||||
_history,
|
|
||||||
Api::SendProgressType::ChooseSticker);
|
|
||||||
}
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
_fieldAutocomplete->setSendMenuDetails([=] {
|
|
||||||
return sendMenuDetails();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_supportAutocomplete) {
|
if (_supportAutocomplete) {
|
||||||
supportInitAutocomplete();
|
supportInitAutocomplete();
|
||||||
}
|
}
|
||||||
_field->rawTextEdit()->installEventFilter(this);
|
_field->rawTextEdit()->installEventFilter(this);
|
||||||
_field->rawTextEdit()->installEventFilter(_fieldAutocomplete);
|
|
||||||
_field->setMimeDataHook([=](
|
_field->setMimeDataHook([=](
|
||||||
not_null<const QMimeData*> data,
|
not_null<const QMimeData*> data,
|
||||||
Ui::InputField::MimeAction action) {
|
Ui::InputField::MimeAction action) {
|
||||||
|
@ -566,15 +481,6 @@ HistoryWidget::HistoryWidget(
|
||||||
Unexpected("action in MimeData hook.");
|
Unexpected("action in MimeData hook.");
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto allow = [=](const auto&) {
|
|
||||||
return _peer && _peer->isSelf();
|
|
||||||
};
|
|
||||||
const auto suggestions = Ui::Emoji::SuggestionsController::Init(
|
|
||||||
this,
|
|
||||||
_field,
|
|
||||||
&controller->session(),
|
|
||||||
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
|
|
||||||
_raiseEmojiSuggestions = [=] { suggestions->raise(); };
|
|
||||||
updateFieldSubmitSettings();
|
updateFieldSubmitSettings();
|
||||||
|
|
||||||
_field->hide();
|
_field->hide();
|
||||||
|
@ -699,9 +605,6 @@ HistoryWidget::HistoryWidget(
|
||||||
updateControlsVisibility();
|
updateControlsVisibility();
|
||||||
updateControlsGeometry();
|
updateControlsGeometry();
|
||||||
}
|
}
|
||||||
if (_fieldAutocomplete->clearFilteredBotCommands()) {
|
|
||||||
checkFieldAutocomplete();
|
|
||||||
}
|
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
using EntryUpdateFlag = Data::EntryUpdate::Flag;
|
using EntryUpdateFlag = Data::EntryUpdate::Flag;
|
||||||
|
@ -834,7 +737,6 @@ HistoryWidget::HistoryWidget(
|
||||||
return update.flags;
|
return update.flags;
|
||||||
}) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
|
}) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
|
||||||
if (flags & PeerUpdateFlag::Rights) {
|
if (flags & PeerUpdateFlag::Rights) {
|
||||||
updateStickersByEmoji();
|
|
||||||
updateFieldPlaceholder();
|
updateFieldPlaceholder();
|
||||||
_preview->checkNow(false);
|
_preview->checkNow(false);
|
||||||
|
|
||||||
|
@ -1191,35 +1093,10 @@ void HistoryWidget::initTabbedSelector() {
|
||||||
|
|
||||||
rpl::merge(
|
rpl::merge(
|
||||||
selector->fileChosen() | filter,
|
selector->fileChosen() | filter,
|
||||||
_fieldAutocomplete->stickerChosen(),
|
|
||||||
selector->customEmojiChosen() | filter,
|
selector->customEmojiChosen() | filter,
|
||||||
controller()->stickerOrEmojiChosen() | filter
|
controller()->stickerOrEmojiChosen() | filter
|
||||||
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
) | rpl::start_with_next([=](ChatHelpers::FileChosen &&data) {
|
||||||
controller()->hideLayer(anim::type::normal);
|
fileChosen(std::move(data));
|
||||||
if (const auto info = data.document->sticker()
|
|
||||||
; info && info->setType == Data::StickersType::Emoji) {
|
|
||||||
if (data.document->isPremiumEmoji()
|
|
||||||
&& !session().premium()
|
|
||||||
&& (!_peer
|
|
||||||
|| !Data::AllowEmojiWithoutPremium(
|
|
||||||
_peer,
|
|
||||||
data.document))) {
|
|
||||||
showPremiumToast(data.document);
|
|
||||||
} else if (!_field->isHidden()) {
|
|
||||||
Data::InsertCustomEmoji(_field.data(), data.document);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
controller()->sendingAnimation().appendSending(
|
|
||||||
data.messageSendingFrom);
|
|
||||||
const auto localId = data.messageSendingFrom.localId;
|
|
||||||
auto messageToSend = Api::MessageToSend(
|
|
||||||
prepareSendAction(data.options));
|
|
||||||
messageToSend.textWithTags = base::take(data.caption);
|
|
||||||
sendExistingDocument(
|
|
||||||
data.document,
|
|
||||||
std::move(messageToSend),
|
|
||||||
localId);
|
|
||||||
}
|
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
selector->photoChosen(
|
selector->photoChosen(
|
||||||
|
@ -1481,66 +1358,95 @@ int HistoryWidget::itemTopForHighlight(
|
||||||
return itemTop;
|
return itemTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::start() {
|
void HistoryWidget::initFieldAutocomplete() {
|
||||||
session().data().stickers().updated(
|
_emojiSuggestions = nullptr;
|
||||||
Data::StickersType::Stickers
|
_autocomplete = nullptr;
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
updateStickersByEmoji();
|
|
||||||
}, lifetime());
|
|
||||||
session().data().stickers().notifySavedGifsUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryWidget::insertHashtagOrBotCommand(
|
|
||||||
QString str,
|
|
||||||
ChatHelpers::FieldAutocompleteChooseMethod method) {
|
|
||||||
if (!_peer) {
|
if (!_peer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto processShortcut = [=](QString shortcut) {
|
||||||
// Send bot command at once, if it was not inserted by pressing Tab.
|
if (!_peer) {
|
||||||
using Method = ChatHelpers::FieldAutocompleteChooseMethod;
|
return;
|
||||||
if (str.at(0) == '/' && method != Method::ByTab) {
|
}
|
||||||
sendBotCommand({ _peer, str, FullMsgId(), replyTo() });
|
const auto messages = &_peer->owner().shortcutMessages();
|
||||||
session().api().finishForwarding(prepareSendAction({}));
|
const auto shortcutId = messages->lookupShortcutId(shortcut);
|
||||||
setFieldText(_field->getTextWithTagsPart(_field->textCursor().position()));
|
if (shortcut.isEmpty()) {
|
||||||
} else {
|
controller()->showSettings(Settings::QuickRepliesId());
|
||||||
_field->insertTag(str);
|
} else if (!_peer->session().premium()) {
|
||||||
}
|
ShowPremiumPreviewToBuy(
|
||||||
|
controller(),
|
||||||
|
PremiumFeature::QuickReplies);
|
||||||
|
} else if (shortcutId) {
|
||||||
|
session().api().sendShortcutMessages(_peer, shortcutId);
|
||||||
|
session().api().finishForwarding(prepareSendAction({}));
|
||||||
|
setFieldText(_field->getTextWithTagsPart(
|
||||||
|
_field->textCursor().position()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
|
||||||
|
.parent = this,
|
||||||
|
.show = controller()->uiShow(),
|
||||||
|
.field = _field.data(),
|
||||||
|
.peer = _peer,
|
||||||
|
.features = [=] {
|
||||||
|
auto result = ChatHelpers::ComposeFeatures();
|
||||||
|
if (_showAnimation
|
||||||
|
|| isChoosingTheme()
|
||||||
|
|| (_inlineBot && !_inlineLookingUpBot)) {
|
||||||
|
result.autocompleteMentions = false;
|
||||||
|
result.autocompleteHashtags = false;
|
||||||
|
result.autocompleteCommands = false;
|
||||||
|
}
|
||||||
|
if (_editMsgId) {
|
||||||
|
result.autocompleteCommands = false;
|
||||||
|
result.suggestStickersByEmoji = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
.sendMenuDetails = [=] { return sendMenuDetails(); },
|
||||||
|
.stickerChoosing = [=] {
|
||||||
|
if (_history) {
|
||||||
|
session().sendProgressManager().update(
|
||||||
|
_history,
|
||||||
|
Api::SendProgressType::ChooseSticker);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.stickerChosen = [=](ChatHelpers::FileChosen &&data) {
|
||||||
|
fileChosen(std::move(data));
|
||||||
|
},
|
||||||
|
.setText = [=](TextWithTags text) { if (_peer) setFieldText(text); },
|
||||||
|
.sendBotCommand = [=](QString command) {
|
||||||
|
if (_peer) {
|
||||||
|
sendBotCommand({ _peer, command, FullMsgId(), replyTo() });
|
||||||
|
session().api().finishForwarding(prepareSendAction({}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.processShortcut = processShortcut,
|
||||||
|
.moderateKeyActivateCallback = [=](int key) {
|
||||||
|
const auto context = [=](FullMsgId itemId) {
|
||||||
|
return _list->prepareClickContext(Qt::LeftButton, itemId);
|
||||||
|
};
|
||||||
|
return !_keyboard->isHidden() && _keyboard->moderateKeyActivate(
|
||||||
|
key,
|
||||||
|
context);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const auto allow = [=](const auto&) {
|
||||||
|
return _peer->isSelf();
|
||||||
|
};
|
||||||
|
_emojiSuggestions.reset(Ui::Emoji::SuggestionsController::Init(
|
||||||
|
this,
|
||||||
|
_field,
|
||||||
|
&controller()->session(),
|
||||||
|
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
InlineBotQuery HistoryWidget::parseInlineBotQuery() const {
|
InlineBotQuery HistoryWidget::parseInlineBotQuery() const {
|
||||||
return (isChoosingTheme() || _editMsgId)
|
return (isChoosingTheme() || _editMsgId)
|
||||||
? InlineBotQuery()
|
? InlineBotQuery()
|
||||||
: ParseInlineBotQuery(&session(), _field);
|
: ParseInlineBotQuery(&session(), _field);
|
||||||
}
|
}
|
||||||
|
|
||||||
AutocompleteQuery HistoryWidget::parseMentionHashtagBotCommandQuery() const {
|
|
||||||
const auto result = (isChoosingTheme()
|
|
||||||
|| (_inlineBot && !_inlineLookingUpBot))
|
|
||||||
? AutocompleteQuery()
|
|
||||||
: ParseMentionHashtagBotCommandQuery(_field, {});
|
|
||||||
if (result.query.isEmpty()) {
|
|
||||||
return result;
|
|
||||||
} else if (result.query[0] == '#'
|
|
||||||
&& cRecentWriteHashtags().isEmpty()
|
|
||||||
&& cRecentSearchHashtags().isEmpty()) {
|
|
||||||
session().local().readRecentHashtagsAndBots();
|
|
||||||
} else if (result.query[0] == '@'
|
|
||||||
&& cRecentInlineBots().isEmpty()) {
|
|
||||||
session().local().readRecentHashtagsAndBots();
|
|
||||||
} else if (result.query[0] == '/') {
|
|
||||||
if (_editMsgId) {
|
|
||||||
return {};
|
|
||||||
} else if (_peer->isUser()
|
|
||||||
&& !_peer->asUser()->isBot()
|
|
||||||
&& _peer->owner().shortcutMessages().shortcuts().list.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryWidget::updateInlineBotQuery() {
|
void HistoryWidget::updateInlineBotQuery() {
|
||||||
if (!_history) {
|
if (!_history) {
|
||||||
return;
|
return;
|
||||||
|
@ -1632,8 +1538,8 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
|
||||||
orderWidgets();
|
orderWidgets();
|
||||||
}
|
}
|
||||||
_inlineResults->queryInlineBot(_inlineBot, _peer, query);
|
_inlineResults->queryInlineBot(_inlineBot, _peer, query);
|
||||||
if (!_fieldAutocomplete->isHidden()) {
|
if (_autocomplete) {
|
||||||
_fieldAutocomplete->hideAnimated();
|
_autocomplete->hideAnimated();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clearInlineBot();
|
clearInlineBot();
|
||||||
|
@ -1665,7 +1571,9 @@ void HistoryWidget::orderWidgets() {
|
||||||
_chooseTheme->raise();
|
_chooseTheme->raise();
|
||||||
}
|
}
|
||||||
_topShadow->raise();
|
_topShadow->raise();
|
||||||
_fieldAutocomplete->raise();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->raise();
|
||||||
|
}
|
||||||
if (_membersDropdown) {
|
if (_membersDropdown) {
|
||||||
_membersDropdown->raise();
|
_membersDropdown->raise();
|
||||||
}
|
}
|
||||||
|
@ -1675,34 +1583,13 @@ void HistoryWidget::orderWidgets() {
|
||||||
if (_tabbedPanel) {
|
if (_tabbedPanel) {
|
||||||
_tabbedPanel->raise();
|
_tabbedPanel->raise();
|
||||||
}
|
}
|
||||||
_raiseEmojiSuggestions();
|
if (_emojiSuggestions) {
|
||||||
|
_emojiSuggestions->raise();
|
||||||
|
}
|
||||||
_attachDragAreas.document->raise();
|
_attachDragAreas.document->raise();
|
||||||
_attachDragAreas.photo->raise();
|
_attachDragAreas.photo->raise();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryWidget::updateStickersByEmoji() {
|
|
||||||
if (!_peer) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto emoji = [&] {
|
|
||||||
const auto errorForStickers = Data::RestrictionError(
|
|
||||||
_peer,
|
|
||||||
ChatRestriction::SendStickers);
|
|
||||||
if (!_editMsgId && !errorForStickers) {
|
|
||||||
const auto &text = _field->getTextWithTags().text;
|
|
||||||
auto length = 0;
|
|
||||||
if (const auto emoji = Ui::Emoji::Find(text, &length)) {
|
|
||||||
if (text.size() <= length) {
|
|
||||||
return emoji;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return EmojiPtr(nullptr);
|
|
||||||
}();
|
|
||||||
_fieldAutocomplete->showStickers(emoji);
|
|
||||||
return (emoji != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryWidget::toggleChooseChatTheme(
|
void HistoryWidget::toggleChooseChatTheme(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
std::optional<bool> show) {
|
std::optional<bool> show) {
|
||||||
|
@ -1745,11 +1632,10 @@ void HistoryWidget::fieldChanged() {
|
||||||
|
|
||||||
InvokeQueued(this, [=] {
|
InvokeQueued(this, [=] {
|
||||||
updateInlineBotQuery();
|
updateInlineBotQuery();
|
||||||
const auto choosingSticker = updateStickersByEmoji();
|
|
||||||
if (_history
|
if (_history
|
||||||
&& !_inlineBot
|
&& !_inlineBot
|
||||||
&& !_editMsgId
|
&& !_editMsgId
|
||||||
&& !choosingSticker
|
&& (!_autocomplete || !_autocomplete->stickersEmoji())
|
||||||
&& updateTyping) {
|
&& updateTyping) {
|
||||||
session().sendProgressManager().update(
|
session().sendProgressManager().update(
|
||||||
_history,
|
_history,
|
||||||
|
@ -1839,6 +1725,34 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryWidget::fileChosen(ChatHelpers::FileChosen &&data) {
|
||||||
|
controller()->hideLayer(anim::type::normal);
|
||||||
|
if (const auto info = data.document->sticker()
|
||||||
|
; info && info->setType == Data::StickersType::Emoji) {
|
||||||
|
if (data.document->isPremiumEmoji()
|
||||||
|
&& !session().premium()
|
||||||
|
&& (!_peer
|
||||||
|
|| !Data::AllowEmojiWithoutPremium(
|
||||||
|
_peer,
|
||||||
|
data.document))) {
|
||||||
|
showPremiumToast(data.document);
|
||||||
|
} else if (!_field->isHidden()) {
|
||||||
|
Data::InsertCustomEmoji(_field.data(), data.document);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
controller()->sendingAnimation().appendSending(
|
||||||
|
data.messageSendingFrom);
|
||||||
|
const auto localId = data.messageSendingFrom.localId;
|
||||||
|
auto messageToSend = Api::MessageToSend(
|
||||||
|
prepareSendAction(data.options));
|
||||||
|
messageToSend.textWithTags = base::take(data.caption);
|
||||||
|
sendExistingDocument(
|
||||||
|
data.document,
|
||||||
|
std::move(messageToSend),
|
||||||
|
localId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryWidget::saveCloudDraft() {
|
void HistoryWidget::saveCloudDraft() {
|
||||||
controller()->session().api().saveCurrentDraftToCloud();
|
controller()->session().api().saveCurrentDraftToCloud();
|
||||||
}
|
}
|
||||||
|
@ -2043,7 +1957,11 @@ void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||||
InvokeQueued(this, [=] { updateStickersByEmoji(); });
|
InvokeQueued(this, [=] {
|
||||||
|
if (_autocomplete) {
|
||||||
|
_autocomplete->requestStickersUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const auto editDraft = _history ? _history->localEditDraft({}) : nullptr;
|
const auto editDraft = _history ? _history->localEditDraft({}) : nullptr;
|
||||||
const auto draft = editDraft
|
const auto draft = editDraft
|
||||||
|
@ -2380,6 +2298,7 @@ void HistoryWidget::showHistory(
|
||||||
controller()->tabbedSelector()->setCurrentPeer(_peer);
|
controller()->tabbedSelector()->setCurrentPeer(_peer);
|
||||||
}
|
}
|
||||||
refreshTabbedPanel();
|
refreshTabbedPanel();
|
||||||
|
initFieldAutocomplete();
|
||||||
|
|
||||||
if (_peer) {
|
if (_peer) {
|
||||||
_unblock->setText(((_peer->isUser()
|
_unblock->setText(((_peer->isUser()
|
||||||
|
@ -2901,7 +2820,7 @@ void HistoryWidget::refreshSendAsToggle() {
|
||||||
bool HistoryWidget::contentOverlapped(const QRect &globalRect) {
|
bool HistoryWidget::contentOverlapped(const QRect &globalRect) {
|
||||||
return (_attachDragAreas.document->overlaps(globalRect)
|
return (_attachDragAreas.document->overlaps(globalRect)
|
||||||
|| _attachDragAreas.photo->overlaps(globalRect)
|
|| _attachDragAreas.photo->overlaps(globalRect)
|
||||||
|| _fieldAutocomplete->overlaps(globalRect)
|
|| (_autocomplete && _autocomplete->overlaps(globalRect))
|
||||||
|| (_tabbedPanel && _tabbedPanel->overlaps(globalRect))
|
|| (_tabbedPanel && _tabbedPanel->overlaps(globalRect))
|
||||||
|| (_inlineResults && _inlineResults->overlaps(globalRect)));
|
|| (_inlineResults && _inlineResults->overlaps(globalRect)));
|
||||||
}
|
}
|
||||||
|
@ -3005,7 +2924,9 @@ void HistoryWidget::updateControlsVisibility() {
|
||||||
toggle(_botStart);
|
toggle(_botStart);
|
||||||
}
|
}
|
||||||
_kbShown = false;
|
_kbShown = false;
|
||||||
_fieldAutocomplete->hide();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->hide();
|
||||||
|
}
|
||||||
if (_supportAutocomplete) {
|
if (_supportAutocomplete) {
|
||||||
_supportAutocomplete->hide();
|
_supportAutocomplete->hide();
|
||||||
}
|
}
|
||||||
|
@ -3049,7 +2970,9 @@ void HistoryWidget::updateControlsVisibility() {
|
||||||
}
|
}
|
||||||
hideFieldIfVisible();
|
hideFieldIfVisible();
|
||||||
} else if (editingMessage() || _canSendMessages) {
|
} else if (editingMessage() || _canSendMessages) {
|
||||||
checkFieldAutocomplete();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->requestRefresh();
|
||||||
|
}
|
||||||
_unblock->hide();
|
_unblock->hide();
|
||||||
_botStart->hide();
|
_botStart->hide();
|
||||||
_joinChannel->hide();
|
_joinChannel->hide();
|
||||||
|
@ -3156,7 +3079,9 @@ void HistoryWidget::updateControlsVisibility() {
|
||||||
_fieldBarCancel->hide();
|
_fieldBarCancel->hide();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_fieldAutocomplete->hide();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->hide();
|
||||||
|
}
|
||||||
if (_supportAutocomplete) {
|
if (_supportAutocomplete) {
|
||||||
_supportAutocomplete->hide();
|
_supportAutocomplete->hide();
|
||||||
}
|
}
|
||||||
|
@ -4157,7 +4082,9 @@ void HistoryWidget::hideChildWidgets() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::hideSelectorControlsAnimated() {
|
void HistoryWidget::hideSelectorControlsAnimated() {
|
||||||
_fieldAutocomplete->hideAnimated();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->hideAnimated();
|
||||||
|
}
|
||||||
if (_supportAutocomplete) {
|
if (_supportAutocomplete) {
|
||||||
_supportAutocomplete->hide();
|
_supportAutocomplete->hide();
|
||||||
}
|
}
|
||||||
|
@ -4992,10 +4919,10 @@ bool HistoryWidget::updateCmdStartShown() {
|
||||||
},
|
},
|
||||||
.source = InlineBots::WebViewSourceBotMenu(),
|
.source = InlineBots::WebViewSourceBotMenu(),
|
||||||
});
|
});
|
||||||
} else if (!_fieldAutocomplete->isHidden()) {
|
} else if (_autocomplete && !_autocomplete->isHidden()) {
|
||||||
_fieldAutocomplete->hideAnimated();
|
_autocomplete->hideAnimated();
|
||||||
} else {
|
} else if (_autocomplete) {
|
||||||
_fieldAutocomplete->showFiltered(_peer, "/", true);
|
_autocomplete->showFiltered(_peer, "/", true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_botMenu.button->widthValue(
|
_botMenu.button->widthValue(
|
||||||
|
@ -5458,7 +5385,9 @@ void HistoryWidget::clearInlineBot() {
|
||||||
if (_inlineResults) {
|
if (_inlineResults) {
|
||||||
_inlineResults->clearInlineBot();
|
_inlineResults->clearInlineBot();
|
||||||
}
|
}
|
||||||
checkFieldAutocomplete();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->requestRefresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::inlineBotChanged() {
|
void HistoryWidget::inlineBotChanged() {
|
||||||
|
@ -5483,18 +5412,6 @@ void HistoryWidget::fieldFocused() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::checkFieldAutocomplete() {
|
|
||||||
if (!_history || _showAnimation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto autocomplete = parseMentionHashtagBotCommandQuery();
|
|
||||||
_fieldAutocomplete->showFiltered(
|
|
||||||
_peer,
|
|
||||||
autocomplete.query,
|
|
||||||
autocomplete.fromStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryWidget::updateFieldPlaceholder() {
|
void HistoryWidget::updateFieldPlaceholder() {
|
||||||
if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) {
|
if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) {
|
||||||
_field->setPlaceholder(
|
_field->setPlaceholder(
|
||||||
|
@ -5968,7 +5885,9 @@ void HistoryWidget::updateControlsGeometry() {
|
||||||
const auto scrollAreaTop = businessBotTop + (_businessBotStatus ? _businessBotStatus->bar().height() : 0);
|
const auto scrollAreaTop = businessBotTop + (_businessBotStatus ? _businessBotStatus->bar().height() : 0);
|
||||||
if (_scroll->y() != scrollAreaTop) {
|
if (_scroll->y() != scrollAreaTop) {
|
||||||
_scroll->moveToLeft(0, scrollAreaTop);
|
_scroll->moveToLeft(0, scrollAreaTop);
|
||||||
_fieldAutocomplete->setBoundings(_scroll->geometry());
|
if (_autocomplete) {
|
||||||
|
_autocomplete->setBoundings(_scroll->geometry());
|
||||||
|
}
|
||||||
if (_supportAutocomplete) {
|
if (_supportAutocomplete) {
|
||||||
_supportAutocomplete->setBoundings(_scroll->geometry());
|
_supportAutocomplete->setBoundings(_scroll->geometry());
|
||||||
}
|
}
|
||||||
|
@ -6245,7 +6164,9 @@ void HistoryWidget::updateHistoryGeometry(
|
||||||
visibleAreaUpdated();
|
visibleAreaUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
_fieldAutocomplete->setBoundings(_scroll->geometry());
|
if (_autocomplete) {
|
||||||
|
_autocomplete->setBoundings(_scroll->geometry());
|
||||||
|
}
|
||||||
if (_supportAutocomplete) {
|
if (_supportAutocomplete) {
|
||||||
_supportAutocomplete->setBoundings(_scroll->geometry());
|
_supportAutocomplete->setBoundings(_scroll->geometry());
|
||||||
}
|
}
|
||||||
|
@ -6939,9 +6860,6 @@ bool HistoryWidget::showSlowmodeError() {
|
||||||
void HistoryWidget::fieldTabbed() {
|
void HistoryWidget::fieldTabbed() {
|
||||||
if (_supportAutocomplete) {
|
if (_supportAutocomplete) {
|
||||||
_supportAutocomplete->activate(_field.data());
|
_supportAutocomplete->activate(_field.data());
|
||||||
} else if (!_fieldAutocomplete->isHidden()) {
|
|
||||||
_fieldAutocomplete->chooseSelected(
|
|
||||||
ChatHelpers::FieldAutocomplete::ChooseMethod::ByTab);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7500,7 +7418,7 @@ bool HistoryWidget::sendExistingDocument(
|
||||||
document,
|
document,
|
||||||
localId);
|
localId);
|
||||||
|
|
||||||
if (_fieldAutocomplete->stickersShown()) {
|
if (_autocomplete && _autocomplete->stickersShown()) {
|
||||||
clearFieldText();
|
clearFieldText();
|
||||||
//_saveDraftText = true;
|
//_saveDraftText = true;
|
||||||
//_saveDraftStart = crl::now();
|
//_saveDraftStart = crl::now();
|
||||||
|
@ -7986,7 +7904,9 @@ void HistoryWidget::fullInfoUpdated() {
|
||||||
if (updateCanSendMessage()) {
|
if (updateCanSendMessage()) {
|
||||||
refresh = true;
|
refresh = true;
|
||||||
}
|
}
|
||||||
checkFieldAutocomplete();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->requestRefresh();
|
||||||
|
}
|
||||||
_list->refreshAboutView();
|
_list->refreshAboutView();
|
||||||
_list->updateBotInfo();
|
_list->updateBotInfo();
|
||||||
|
|
||||||
|
@ -8129,8 +8049,8 @@ void HistoryWidget::escape() {
|
||||||
} else {
|
} else {
|
||||||
cancelEdit();
|
cancelEdit();
|
||||||
}
|
}
|
||||||
} else if (!_fieldAutocomplete->isHidden()) {
|
} else if (_autocomplete && !_autocomplete->isHidden()) {
|
||||||
_fieldAutocomplete->hideAnimated();
|
_autocomplete->hideAnimated();
|
||||||
} else if (_replyTo && _field->getTextWithTags().text.isEmpty()) {
|
} else if (_replyTo && _field->getTextWithTags().text.isEmpty()) {
|
||||||
cancelReply();
|
cancelReply();
|
||||||
} else if (auto &voice = _voiceRecordBar; voice->isActive()) {
|
} else if (auto &voice = _voiceRecordBar; voice->isActive()) {
|
||||||
|
|
|
@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
enum class SendMediaType;
|
enum class SendMediaType;
|
||||||
class MessageLinksParser;
|
class MessageLinksParser;
|
||||||
struct InlineBotQuery;
|
struct InlineBotQuery;
|
||||||
struct AutocompleteQuery;
|
|
||||||
|
|
||||||
namespace MTP {
|
namespace MTP {
|
||||||
class Error;
|
class Error;
|
||||||
|
@ -76,6 +75,10 @@ class ContinuousScroll;
|
||||||
struct ChatPaintHighlight;
|
struct ChatPaintHighlight;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Ui::Emoji {
|
||||||
|
class SuggestionsController;
|
||||||
|
} // namespace Ui::Emoji
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
@ -84,7 +87,7 @@ namespace ChatHelpers {
|
||||||
class TabbedPanel;
|
class TabbedPanel;
|
||||||
class TabbedSelector;
|
class TabbedSelector;
|
||||||
class FieldAutocomplete;
|
class FieldAutocomplete;
|
||||||
enum class FieldAutocompleteChooseMethod;
|
struct FileChosen;
|
||||||
} // namespace ChatHelpers
|
} // namespace ChatHelpers
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
@ -126,8 +129,6 @@ public:
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> controller);
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
void start();
|
|
||||||
|
|
||||||
void historyLoaded();
|
void historyLoaded();
|
||||||
|
|
||||||
[[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const;
|
[[nodiscard]] bool preventsClose(Fn<void()> &&continueCallback) const;
|
||||||
|
@ -159,7 +160,6 @@ public:
|
||||||
|
|
||||||
bool updateReplaceMediaButton();
|
bool updateReplaceMediaButton();
|
||||||
void updateFieldPlaceholder();
|
void updateFieldPlaceholder();
|
||||||
bool updateStickersByEmoji();
|
|
||||||
|
|
||||||
bool confirmSendingFiles(const QStringList &files);
|
bool confirmSendingFiles(const QStringList &files);
|
||||||
bool confirmSendingFiles(not_null<const QMimeData*> data);
|
bool confirmSendingFiles(not_null<const QMimeData*> data);
|
||||||
|
@ -367,17 +367,15 @@ private:
|
||||||
void fieldFocused();
|
void fieldFocused();
|
||||||
void fieldResized();
|
void fieldResized();
|
||||||
|
|
||||||
void insertHashtagOrBotCommand(
|
void initFieldAutocomplete();
|
||||||
QString str,
|
|
||||||
ChatHelpers::FieldAutocompleteChooseMethod method);
|
|
||||||
void cancelInlineBot();
|
void cancelInlineBot();
|
||||||
void saveDraft(bool delayed = false);
|
void saveDraft(bool delayed = false);
|
||||||
void saveCloudDraft();
|
void saveCloudDraft();
|
||||||
void saveDraftDelayed();
|
void saveDraftDelayed();
|
||||||
void checkFieldAutocomplete();
|
|
||||||
void showMembersDropdown();
|
void showMembersDropdown();
|
||||||
void windowIsVisibleChanged();
|
void windowIsVisibleChanged();
|
||||||
void saveFieldToHistoryLocalDraft();
|
void saveFieldToHistoryLocalDraft();
|
||||||
|
void fileChosen(ChatHelpers::FileChosen &&data);
|
||||||
|
|
||||||
void updateFieldSubmitSettings();
|
void updateFieldSubmitSettings();
|
||||||
|
|
||||||
|
@ -485,8 +483,6 @@ private:
|
||||||
void orderWidgets();
|
void orderWidgets();
|
||||||
|
|
||||||
[[nodiscard]] InlineBotQuery parseInlineBotQuery() const;
|
[[nodiscard]] InlineBotQuery parseInlineBotQuery() const;
|
||||||
[[nodiscard]] auto parseMentionHashtagBotCommandQuery() const
|
|
||||||
-> AutocompleteQuery;
|
|
||||||
|
|
||||||
void clearInlineBot();
|
void clearInlineBot();
|
||||||
void inlineBotChanged();
|
void inlineBotChanged();
|
||||||
|
@ -740,7 +736,8 @@ private:
|
||||||
|
|
||||||
HistoryView::CornerButtons _cornerButtons;
|
HistoryView::CornerButtons _cornerButtons;
|
||||||
|
|
||||||
const object_ptr<ChatHelpers::FieldAutocomplete> _fieldAutocomplete;
|
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||||
|
std::unique_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions;
|
||||||
object_ptr<Support::Autocomplete> _supportAutocomplete;
|
object_ptr<Support::Autocomplete> _supportAutocomplete;
|
||||||
|
|
||||||
UserData *_inlineBot = nullptr;
|
UserData *_inlineBot = nullptr;
|
||||||
|
@ -807,8 +804,6 @@ private:
|
||||||
|
|
||||||
DragArea::Areas _attachDragAreas;
|
DragArea::Areas _attachDragAreas;
|
||||||
|
|
||||||
Fn<void()> _raiseEmojiSuggestions;
|
|
||||||
|
|
||||||
bool _nonEmptySelection = false;
|
bool _nonEmptySelection = false;
|
||||||
|
|
||||||
TextUpdateEvents _textUpdateEvents = (TextUpdateEvents() | TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping);
|
TextUpdateEvents _textUpdateEvents = (TextUpdateEvents() | TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping);
|
||||||
|
|
|
@ -859,10 +859,6 @@ ComposeControls::ComposeControls(
|
||||||
_wrap.get(),
|
_wrap.get(),
|
||||||
st::historyBotCommandStart)
|
st::historyBotCommandStart)
|
||||||
: nullptr)
|
: nullptr)
|
||||||
, _autocomplete(std::make_unique<ChatHelpers::FieldAutocomplete>(
|
|
||||||
parent,
|
|
||||||
_show,
|
|
||||||
&_st.tabbed))
|
|
||||||
, _header(std::make_unique<FieldHeader>(
|
, _header(std::make_unique<FieldHeader>(
|
||||||
_wrap.get(),
|
_wrap.get(),
|
||||||
_show,
|
_show,
|
||||||
|
@ -963,6 +959,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
||||||
_header->setHistory(args);
|
_header->setHistory(args);
|
||||||
registerDraftSource();
|
registerDraftSource();
|
||||||
_selector->setCurrentPeer(history ? history->peer.get() : nullptr);
|
_selector->setCurrentPeer(history ? history->peer.get() : nullptr);
|
||||||
|
initFieldAutocomplete();
|
||||||
initWebpageProcess();
|
initWebpageProcess();
|
||||||
initWriteRestriction();
|
initWriteRestriction();
|
||||||
initForwardProcess();
|
initForwardProcess();
|
||||||
|
@ -1238,8 +1235,8 @@ void ComposeControls::raisePanels() {
|
||||||
if (_attachBotsMenu) {
|
if (_attachBotsMenu) {
|
||||||
_attachBotsMenu->raise();
|
_attachBotsMenu->raise();
|
||||||
}
|
}
|
||||||
if (_raiseEmojiSuggestions) {
|
if (_emojiSuggestions) {
|
||||||
_raiseEmojiSuggestions();
|
_emojiSuggestions->raise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1323,35 +1320,6 @@ void ComposeControls::hidePanelsAnimated() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::checkAutocomplete() {
|
|
||||||
if (!_history) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto peer = _history->peer;
|
|
||||||
const auto autocomplete = _isInlineBot
|
|
||||||
? AutocompleteQuery()
|
|
||||||
: ParseMentionHashtagBotCommandQuery(_field, _features);
|
|
||||||
if (!autocomplete.query.isEmpty()) {
|
|
||||||
if (autocomplete.query[0] == '#'
|
|
||||||
&& cRecentWriteHashtags().isEmpty()
|
|
||||||
&& cRecentSearchHashtags().isEmpty()) {
|
|
||||||
peer->session().local().readRecentHashtagsAndBots();
|
|
||||||
} else if (autocomplete.query[0] == '@'
|
|
||||||
&& cRecentInlineBots().isEmpty()) {
|
|
||||||
peer->session().local().readRecentHashtagsAndBots();
|
|
||||||
} else if (autocomplete.query[0] == '/'
|
|
||||||
&& peer->isUser()
|
|
||||||
&& !peer->asUser()->isBot()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_autocomplete->showFiltered(
|
|
||||||
peer,
|
|
||||||
autocomplete.query,
|
|
||||||
autocomplete.fromStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ComposeControls::hide() {
|
void ComposeControls::hide() {
|
||||||
showStarted();
|
showStarted();
|
||||||
_hidden = true;
|
_hidden = true;
|
||||||
|
@ -1361,7 +1329,9 @@ void ComposeControls::show() {
|
||||||
if (_hidden.current()) {
|
if (_hidden.current()) {
|
||||||
_hidden = false;
|
_hidden = false;
|
||||||
showFinished();
|
showFinished();
|
||||||
checkAutocomplete();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->requestRefresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1630,10 +1600,6 @@ void ComposeControls::initField() {
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
escape();
|
escape();
|
||||||
}, _field->lifetime());
|
}, _field->lifetime());
|
||||||
_field->tabbed(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
fieldTabbed();
|
|
||||||
}, _field->lifetime());
|
|
||||||
_field->heightChanges(
|
_field->heightChanges(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
updateHeight();
|
updateHeight();
|
||||||
|
@ -1664,21 +1630,6 @@ void ComposeControls::initField() {
|
||||||
_field->setEditLinkCallback(
|
_field->setEditLinkCallback(
|
||||||
DefaultEditLinkCallback(_show, _field, &_st.boxField));
|
DefaultEditLinkCallback(_show, _field, &_st.boxField));
|
||||||
_field->setEditLanguageCallback(DefaultEditLanguageCallback(_show));
|
_field->setEditLanguageCallback(DefaultEditLanguageCallback(_show));
|
||||||
initAutocomplete();
|
|
||||||
const auto allow = [=](not_null<DocumentData*> emoji) {
|
|
||||||
return _history
|
|
||||||
&& Data::AllowEmojiWithoutPremium(_history->peer, emoji);
|
|
||||||
};
|
|
||||||
const auto suggestions = Ui::Emoji::SuggestionsController::Init(
|
|
||||||
_panelsParent,
|
|
||||||
_field,
|
|
||||||
_session,
|
|
||||||
{
|
|
||||||
.suggestCustomEmoji = true,
|
|
||||||
.allowCustomWithoutPremium = allow,
|
|
||||||
.st = &_st.suggestions,
|
|
||||||
});
|
|
||||||
_raiseEmojiSuggestions = [=] { suggestions->raise(); };
|
|
||||||
|
|
||||||
const auto rawTextEdit = _field->rawTextEdit().get();
|
const auto rawTextEdit = _field->rawTextEdit().get();
|
||||||
rpl::merge(
|
rpl::merge(
|
||||||
|
@ -1698,124 +1649,65 @@ void ComposeControls::updateSubmitSettings() {
|
||||||
_field->setSubmitSettings(settings);
|
_field->setSubmitSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::initAutocomplete() {
|
void ComposeControls::initFieldAutocomplete() {
|
||||||
const auto insertHashtagOrBotCommand = [=](
|
_emojiSuggestions = nullptr;
|
||||||
const QString &string,
|
_autocomplete = nullptr;
|
||||||
ChatHelpers::FieldAutocompleteChooseMethod method) {
|
if (!_history) {
|
||||||
// Send bot command at once, if it was not inserted by pressing Tab.
|
return;
|
||||||
using Method = ChatHelpers::FieldAutocompleteChooseMethod;
|
}
|
||||||
if (string.at(0) == '/' && method != Method::ByTab) {
|
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
|
||||||
_sendCommandRequests.fire_copy(string);
|
.parent = _parent,
|
||||||
setText(
|
.show = _show,
|
||||||
_field->getTextWithTagsPart(_field->textCursor().position()));
|
.field = _field.get(),
|
||||||
} else {
|
.stOverride = &_st.tabbed,
|
||||||
_field->insertTag(string);
|
.peer = _history->peer,
|
||||||
}
|
.features = [=] {
|
||||||
};
|
auto result = _features;
|
||||||
|
if (_inlineBot && !_inlineLookingUpBot) {
|
||||||
_autocomplete->mentionChosen(
|
result.autocompleteMentions = false;
|
||||||
) | rpl::start_with_next([=](
|
result.autocompleteHashtags = false;
|
||||||
ChatHelpers::FieldAutocomplete::MentionChosen data) {
|
result.autocompleteCommands = false;
|
||||||
const auto user = data.user;
|
}
|
||||||
if (data.mention.isEmpty()) {
|
if (isEditingMessage()) {
|
||||||
_field->insertTag(
|
result.autocompleteCommands = false;
|
||||||
user->firstName.isEmpty() ? user->name() : user->firstName,
|
result.suggestStickersByEmoji = false;
|
||||||
PrepareMentionTag(user));
|
}
|
||||||
} else {
|
return result;
|
||||||
_field->insertTag('@' + data.mention);
|
},
|
||||||
}
|
.sendMenuDetails = [=] { return sendMenuDetails(); },
|
||||||
}, _autocomplete->lifetime());
|
.stickerChoosing = [=] {
|
||||||
|
|
||||||
_autocomplete->hashtagChosen(
|
|
||||||
) | rpl::start_with_next([=](
|
|
||||||
ChatHelpers::FieldAutocomplete::HashtagChosen data) {
|
|
||||||
insertHashtagOrBotCommand(data.hashtag, data.method);
|
|
||||||
}, _autocomplete->lifetime());
|
|
||||||
|
|
||||||
_autocomplete->botCommandChosen(
|
|
||||||
) | rpl::start_with_next([=](
|
|
||||||
ChatHelpers::FieldAutocomplete::BotCommandChosen data) {
|
|
||||||
insertHashtagOrBotCommand(data.command, data.method);
|
|
||||||
}, _autocomplete->lifetime());
|
|
||||||
|
|
||||||
_autocomplete->stickerChosen(
|
|
||||||
) | rpl::start_with_next([=](
|
|
||||||
ChatHelpers::FieldAutocomplete::StickerChosen data) {
|
|
||||||
if (!_showSlowmodeError || !_showSlowmodeError()) {
|
|
||||||
setText({});
|
|
||||||
}
|
|
||||||
//_saveDraftText = true;
|
|
||||||
//_saveDraftStart = crl::now();
|
|
||||||
//saveDraft();
|
|
||||||
//saveCloudDraft(); // won't be needed if SendInlineBotResult will clear the cloud draft
|
|
||||||
_fileChosen.fire(std::move(data));
|
|
||||||
}, _autocomplete->lifetime());
|
|
||||||
|
|
||||||
_autocomplete->choosingProcesses(
|
|
||||||
) | rpl::start_with_next([=](ChatHelpers::FieldAutocomplete::Type type) {
|
|
||||||
if (type == ChatHelpers::FieldAutocomplete::Type::Stickers) {
|
|
||||||
_sendActionUpdates.fire({
|
_sendActionUpdates.fire({
|
||||||
.type = Api::SendProgressType::ChooseSticker,
|
.type = Api::SendProgressType::ChooseSticker,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}, _autocomplete->lifetime());
|
.stickerChosen = [=](ChatHelpers::FileChosen &&data) {
|
||||||
|
if (!_showSlowmodeError || !_showSlowmodeError()) {
|
||||||
_autocomplete->setSendMenuDetails([=] { return sendMenuDetails(); });
|
setText({});
|
||||||
|
|
||||||
//_autocomplete->setModerateKeyActivateCallback([=](int key) {
|
|
||||||
// return _keyboard->isHidden()
|
|
||||||
// ? false
|
|
||||||
// : _keyboard->moderateKeyActivate(key);
|
|
||||||
//});
|
|
||||||
|
|
||||||
_field->rawTextEdit()->installEventFilter(_autocomplete.get());
|
|
||||||
|
|
||||||
_session->data().botCommandsChanges(
|
|
||||||
) | rpl::filter([=](not_null<PeerData*> peer) {
|
|
||||||
return _history && (_history->peer == peer);
|
|
||||||
}) | rpl::start_with_next([=] {
|
|
||||||
if (_autocomplete->clearFilteredBotCommands()) {
|
|
||||||
checkAutocomplete();
|
|
||||||
}
|
|
||||||
}, _autocomplete->lifetime());
|
|
||||||
|
|
||||||
_session->data().stickers().updated(
|
|
||||||
Data::StickersType::Stickers
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
updateStickersByEmoji();
|
|
||||||
}, _autocomplete->lifetime());
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
_field->rawTextEdit(),
|
|
||||||
&QTextEdit::cursorPositionChanged,
|
|
||||||
_autocomplete.get(),
|
|
||||||
[=] { checkAutocomplete(); },
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
|
|
||||||
_autocomplete->hideFast();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ComposeControls::updateStickersByEmoji() {
|
|
||||||
if (!_history) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const auto emoji = [&] {
|
|
||||||
const auto errorForStickers = Data::RestrictionError(
|
|
||||||
_history->peer,
|
|
||||||
ChatRestriction::SendStickers);
|
|
||||||
if (!isEditingMessage() && !errorForStickers) {
|
|
||||||
const auto &text = _field->getTextWithTags().text;
|
|
||||||
auto length = 0;
|
|
||||||
if (const auto emoji = Ui::Emoji::Find(text, &length)) {
|
|
||||||
if (text.size() <= length) {
|
|
||||||
return emoji;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
//_saveDraftText = true;
|
||||||
return EmojiPtr(nullptr);
|
//_saveDraftStart = crl::now();
|
||||||
}();
|
//saveDraft();
|
||||||
_autocomplete->showStickers(emoji);
|
// Won't be needed if SendInlineBotResult clears the cloud draft.
|
||||||
return (emoji != nullptr);
|
//saveCloudDraft();
|
||||||
|
_fileChosen.fire(std::move(data));
|
||||||
|
},
|
||||||
|
.setText = [=](TextWithTags text) { setText(text); },
|
||||||
|
.sendBotCommand = [=](QString command) {
|
||||||
|
_sendCommandRequests.fire_copy(command);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const auto allow = [=](not_null<DocumentData*> emoji) {
|
||||||
|
return Data::AllowEmojiWithoutPremium(_history->peer, emoji);
|
||||||
|
};
|
||||||
|
_emojiSuggestions.reset(Ui::Emoji::SuggestionsController::Init(
|
||||||
|
_panelsParent,
|
||||||
|
_field,
|
||||||
|
_session,
|
||||||
|
{
|
||||||
|
.suggestCustomEmoji = true,
|
||||||
|
.allowCustomWithoutPremium = allow,
|
||||||
|
.st = &_st.suggestions,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::updateFieldPlaceholder() {
|
void ComposeControls::updateFieldPlaceholder() {
|
||||||
|
@ -1872,10 +1764,9 @@ void ComposeControls::fieldChanged() {
|
||||||
updateControlsVisibility();
|
updateControlsVisibility();
|
||||||
updateControlsGeometry(_wrap->size());
|
updateControlsGeometry(_wrap->size());
|
||||||
}
|
}
|
||||||
InvokeQueued(_autocomplete.get(), [=] {
|
InvokeQueued(_field.get(), [=] {
|
||||||
updateInlineBotQuery();
|
updateInlineBotQuery();
|
||||||
const auto choosingSticker = updateStickersByEmoji();
|
if ((!_autocomplete || !_autocomplete->stickersEmoji()) && typing) {
|
||||||
if (!choosingSticker && typing) {
|
|
||||||
_sendActionUpdates.fire({ Api::SendProgressType::Typing });
|
_sendActionUpdates.fire({ Api::SendProgressType::Typing });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2026,7 +1917,11 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||||
? draft->reply.messageId
|
? draft->reply.messageId
|
||||||
: FullMsgId();
|
: FullMsgId();
|
||||||
|
|
||||||
InvokeQueued(_autocomplete.get(), [=] { updateStickersByEmoji(); });
|
InvokeQueued(_autocomplete.get(), [=] {
|
||||||
|
if (_autocomplete) {
|
||||||
|
_autocomplete->requestStickersUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
const auto guard = gsl::finally([&] {
|
const auto guard = gsl::finally([&] {
|
||||||
updateSendButtonType();
|
updateSendButtonType();
|
||||||
updateReplaceMediaButton();
|
updateReplaceMediaButton();
|
||||||
|
@ -2128,13 +2023,6 @@ void ComposeControls::cancelForward() {
|
||||||
updateForwarding();
|
updateForwarding();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::fieldTabbed() {
|
|
||||||
if (!_autocomplete->isHidden()) {
|
|
||||||
_autocomplete->chooseSelected(
|
|
||||||
ChatHelpers::FieldAutocomplete::ChooseMethod::ByTab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<SendActionUpdate> ComposeControls::sendActionUpdates() const {
|
rpl::producer<SendActionUpdate> ComposeControls::sendActionUpdates() const {
|
||||||
return rpl::merge(
|
return rpl::merge(
|
||||||
_sendActionUpdates.events(),
|
_sendActionUpdates.events(),
|
||||||
|
@ -2301,7 +2189,9 @@ void ComposeControls::clearInlineBot() {
|
||||||
if (_inlineResults) {
|
if (_inlineResults) {
|
||||||
_inlineResults->clearInlineBot();
|
_inlineResults->clearInlineBot();
|
||||||
}
|
}
|
||||||
checkAutocomplete();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->requestRefresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::inlineBotChanged() {
|
void ComposeControls::inlineBotChanged() {
|
||||||
|
@ -2310,7 +2200,9 @@ void ComposeControls::inlineBotChanged() {
|
||||||
_isInlineBot = isInlineBot;
|
_isInlineBot = isInlineBot;
|
||||||
updateFieldPlaceholder();
|
updateFieldPlaceholder();
|
||||||
updateSubmitSettings();
|
updateSubmitSettings();
|
||||||
checkAutocomplete();
|
if (_autocomplete) {
|
||||||
|
_autocomplete->requestRefresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2983,7 +2875,9 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_autocomplete) {
|
if (_autocomplete) {
|
||||||
InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); });
|
InvokeQueued(_autocomplete.get(), [=] {
|
||||||
|
_autocomplete->requestRefresh();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3169,7 +3063,6 @@ void ComposeControls::initWebpageProcess() {
|
||||||
}) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
|
}) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
|
||||||
if (flags & Data::PeerUpdate::Flag::Rights) {
|
if (flags & Data::PeerUpdate::Flag::Rights) {
|
||||||
_preview->checkNow(false);
|
_preview->checkNow(false);
|
||||||
updateStickersByEmoji();
|
|
||||||
updateFieldPlaceholder();
|
updateFieldPlaceholder();
|
||||||
}
|
}
|
||||||
if (flags & Data::PeerUpdate::Flag::Notifications) {
|
if (flags & Data::PeerUpdate::Flag::Notifications) {
|
||||||
|
@ -3401,7 +3294,7 @@ void ComposeControls::applyInlineBotQuery(
|
||||||
updateOuterGeometry(_wrap->geometry());
|
updateOuterGeometry(_wrap->geometry());
|
||||||
}
|
}
|
||||||
_inlineResults->queryInlineBot(_inlineBot, _history->peer, query);
|
_inlineResults->queryInlineBot(_inlineBot, _history->peer, query);
|
||||||
if (!_autocomplete->isHidden()) {
|
if (_autocomplete) {
|
||||||
_autocomplete->hideAnimated();
|
_autocomplete->hideAnimated();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -68,6 +68,10 @@ class DropdownMenu;
|
||||||
struct PreparedList;
|
struct PreparedList;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Ui::Emoji {
|
||||||
|
class SuggestionsController;
|
||||||
|
} // namespace Ui::Emoji
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
@ -259,6 +263,7 @@ private:
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void initField();
|
void initField();
|
||||||
|
void initFieldAutocomplete();
|
||||||
void initTabbedSelector();
|
void initTabbedSelector();
|
||||||
void initSendButton();
|
void initSendButton();
|
||||||
void initSendAsButton(not_null<PeerData*> peer);
|
void initSendAsButton(not_null<PeerData*> peer);
|
||||||
|
@ -266,7 +271,6 @@ private:
|
||||||
void initForwardProcess();
|
void initForwardProcess();
|
||||||
void initWriteRestriction();
|
void initWriteRestriction();
|
||||||
void initVoiceRecordBar();
|
void initVoiceRecordBar();
|
||||||
void initAutocomplete();
|
|
||||||
void initKeyHandler();
|
void initKeyHandler();
|
||||||
void updateSubmitSettings();
|
void updateSubmitSettings();
|
||||||
void updateSendButtonType();
|
void updateSendButtonType();
|
||||||
|
@ -290,15 +294,12 @@ private:
|
||||||
SendRequestType requestType = SendRequestType::Text) const;
|
SendRequestType requestType = SendRequestType::Text) const;
|
||||||
|
|
||||||
void orderControls();
|
void orderControls();
|
||||||
void checkAutocomplete();
|
|
||||||
bool updateStickersByEmoji();
|
|
||||||
void updateFieldPlaceholder();
|
void updateFieldPlaceholder();
|
||||||
void updateSilentBroadcast();
|
void updateSilentBroadcast();
|
||||||
void editMessage(not_null<HistoryItem*> item);
|
void editMessage(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
void escape();
|
void escape();
|
||||||
void fieldChanged();
|
void fieldChanged();
|
||||||
void fieldTabbed();
|
|
||||||
void toggleTabbedSelectorMode();
|
void toggleTabbedSelectorMode();
|
||||||
void createTabbedPanel();
|
void createTabbedPanel();
|
||||||
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
|
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
|
||||||
|
@ -392,6 +393,7 @@ private:
|
||||||
std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
|
std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
|
||||||
std::unique_ptr<Ui::DropdownMenu> _attachBotsMenu;
|
std::unique_ptr<Ui::DropdownMenu> _attachBotsMenu;
|
||||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||||
|
std::unique_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions;
|
||||||
|
|
||||||
friend class FieldHeader;
|
friend class FieldHeader;
|
||||||
const std::unique_ptr<FieldHeader> _header;
|
const std::unique_ptr<FieldHeader> _header;
|
||||||
|
@ -441,8 +443,6 @@ private:
|
||||||
|
|
||||||
std::unique_ptr<Controls::WebpageProcessor> _preview;
|
std::unique_ptr<Controls::WebpageProcessor> _preview;
|
||||||
|
|
||||||
Fn<void()> _raiseEmojiSuggestions;
|
|
||||||
|
|
||||||
rpl::lifetime _historyLifetime;
|
rpl::lifetime _historyLifetime;
|
||||||
rpl::lifetime _uploaderSubscriptions;
|
rpl::lifetime _uploaderSubscriptions;
|
||||||
|
|
||||||
|
|
|
@ -422,7 +422,7 @@ MainWidget::MainWidget(
|
||||||
|
|
||||||
cSetOtherOnline(0);
|
cSetOtherOnline(0);
|
||||||
|
|
||||||
_history->start();
|
session().data().stickers().notifySavedGifsUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWidget::~MainWidget() = default;
|
MainWidget::~MainWidget() = default;
|
||||||
|
|
Loading…
Add table
Reference in a new issue