mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 23:53:58 +02:00
Add FieldAutocomplete to ComposeControls.
This commit is contained in:
parent
5d2ffae215
commit
ac02e2be9e
9 changed files with 444 additions and 199 deletions
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/stickers/data_stickers.h"
|
||||
#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "chat_helpers/message_field.h" // PrepareMentionTag.
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lottie/lottie_single_player.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
|
@ -39,15 +41,105 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
class FieldAutocomplete::Inner final
|
||||
: public Ui::RpWidget
|
||||
, private base::Subscriber {
|
||||
|
||||
public:
|
||||
struct ScrollTo {
|
||||
int top;
|
||||
int bottom;
|
||||
};
|
||||
|
||||
Inner(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
not_null<HashtagRows*> hrows,
|
||||
not_null<BotCommandRows*> brows,
|
||||
not_null<StickerRows*> srows);
|
||||
|
||||
void clearSel(bool hidden = false);
|
||||
bool moveSel(int key);
|
||||
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
|
||||
bool chooseAtIndex(
|
||||
FieldAutocomplete::ChooseMethod method,
|
||||
int index,
|
||||
Api::SendOptions options = Api::SendOptions()) const;
|
||||
|
||||
void setRecentInlineBotsInRows(int32 bots);
|
||||
void rowsUpdated();
|
||||
|
||||
rpl::producer<FieldAutocomplete::MentionChosen> mentionChosen() const;
|
||||
rpl::producer<FieldAutocomplete::HashtagChosen> hashtagChosen() const;
|
||||
rpl::producer<FieldAutocomplete::BotCommandChosen>
|
||||
botCommandChosen() const;
|
||||
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
|
||||
rpl::producer<ScrollTo> scrollToRequested() const;
|
||||
|
||||
void onParentGeometryChanged();
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void enterEventHook(QEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
void updateSelectedRow();
|
||||
void setSel(int sel, bool scroll = false);
|
||||
void showPreview();
|
||||
void selectByMouse(QPoint global);
|
||||
|
||||
QSize stickerBoundingBox() const;
|
||||
void setupLottie(StickerSuggestion &suggestion);
|
||||
void repaintSticker(not_null<DocumentData*> document);
|
||||
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<FieldAutocomplete*> _parent;
|
||||
const not_null<MentionRows*> _mrows;
|
||||
const not_null<HashtagRows*> _hrows;
|
||||
const not_null<BotCommandRows*> _brows;
|
||||
const not_null<StickerRows*> _srows;
|
||||
rpl::lifetime _stickersLifetime;
|
||||
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
int _stickersPerRow = 1;
|
||||
int _recentInlineBotsInRows = 0;
|
||||
int _sel = -1;
|
||||
int _down = -1;
|
||||
std::optional<QPoint> _lastMousePosition;
|
||||
bool _mouseSelection = false;
|
||||
|
||||
bool _overDelete = false;
|
||||
|
||||
bool _previewShown = false;
|
||||
|
||||
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
|
||||
rpl::event_stream<FieldAutocomplete::HashtagChosen> _hashtagChosen;
|
||||
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
|
||||
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
|
||||
rpl::event_stream<ScrollTo> _scrollToRequested;
|
||||
|
||||
base::Timer _previewTimer;
|
||||
|
||||
};
|
||||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _scroll(this, st::mentionScroll) {
|
||||
_scroll->setGeometry(rect());
|
||||
hide();
|
||||
|
||||
using Inner = internal::FieldAutocompleteInner;
|
||||
_scroll->setGeometry(rect());
|
||||
|
||||
_inner = _scroll->setOwnedWidget(
|
||||
object_ptr<Inner>(
|
||||
|
@ -76,6 +168,10 @@ FieldAutocomplete::FieldAutocomplete(
|
|||
&Inner::onParentGeometryChanged);
|
||||
}
|
||||
|
||||
not_null<Window::SessionController*> FieldAutocomplete::controller() const {
|
||||
return _controller;
|
||||
}
|
||||
|
||||
auto FieldAutocomplete::mentionChosen() const
|
||||
-> rpl::producer<FieldAutocomplete::MentionChosen> {
|
||||
return _inner->mentionChosen();
|
||||
|
@ -125,9 +221,9 @@ void FieldAutocomplete::showFiltered(
|
|||
if (query.isEmpty()) {
|
||||
_type = Type::Mentions;
|
||||
rowsUpdated(
|
||||
internal::MentionRows(),
|
||||
internal::HashtagRows(),
|
||||
internal::BotCommandRows(),
|
||||
MentionRows(),
|
||||
HashtagRows(),
|
||||
BotCommandRows(),
|
||||
base::take(_srows),
|
||||
false);
|
||||
return;
|
||||
|
@ -171,7 +267,7 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
|
|||
base::take(_mrows),
|
||||
base::take(_hrows),
|
||||
base::take(_brows),
|
||||
internal::StickerRows(),
|
||||
StickerRows(),
|
||||
false);
|
||||
return;
|
||||
}
|
||||
|
@ -203,7 +299,7 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
|||
}
|
||||
}
|
||||
|
||||
internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
||||
FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
||||
const auto list = _controller->session().data().stickers().getListByEmoji(
|
||||
_emoji,
|
||||
_stickersSeed
|
||||
|
@ -211,7 +307,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
|||
auto result = ranges::view::all(
|
||||
list
|
||||
) | ranges::view::transform([](not_null<DocumentData*> sticker) {
|
||||
return internal::StickerSuggestion{
|
||||
return StickerSuggestion{
|
||||
sticker,
|
||||
sticker->createMediaView()
|
||||
};
|
||||
|
@ -223,7 +319,7 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
|||
const auto i = ranges::find(
|
||||
result,
|
||||
suggestion.document,
|
||||
&internal::StickerSuggestion::document);
|
||||
&StickerSuggestion::document);
|
||||
if (i != end(result)) {
|
||||
i->animated = std::move(suggestion.animated);
|
||||
}
|
||||
|
@ -233,10 +329,10 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
|||
|
||||
void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
int32 now = base::unixtime::now(), recentInlineBots = 0;
|
||||
internal::MentionRows mrows;
|
||||
internal::HashtagRows hrows;
|
||||
internal::BotCommandRows brows;
|
||||
internal::StickerRows srows;
|
||||
MentionRows mrows;
|
||||
HashtagRows hrows;
|
||||
BotCommandRows brows;
|
||||
StickerRows srows;
|
||||
if (_emoji) {
|
||||
srows = getStickerSuggestions();
|
||||
} else if (_type == Type::Mentions) {
|
||||
|
@ -435,10 +531,10 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
|||
}
|
||||
|
||||
void FieldAutocomplete::rowsUpdated(
|
||||
internal::MentionRows &&mrows,
|
||||
internal::HashtagRows &&hrows,
|
||||
internal::BotCommandRows &&brows,
|
||||
internal::StickerRows &&srows,
|
||||
MentionRows &&mrows,
|
||||
HashtagRows &&hrows,
|
||||
BotCommandRows &&brows,
|
||||
StickerRows &&srows,
|
||||
bool resetScroll) {
|
||||
if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) {
|
||||
if (!isHidden()) {
|
||||
|
@ -620,9 +716,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
|
|||
return QWidget::eventFilter(obj, e);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
FieldAutocompleteInner::FieldAutocompleteInner(
|
||||
FieldAutocomplete::Inner::Inner(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
|
@ -642,7 +736,7 @@ FieldAutocompleteInner::FieldAutocompleteInner(
|
|||
}, lifetime());
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
QRect r(e->rect());
|
||||
|
@ -841,11 +935,11 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
|||
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowFg);
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) {
|
||||
void FieldAutocomplete::Inner::resizeEvent(QResizeEvent *e) {
|
||||
_stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
|
||||
void FieldAutocomplete::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||
const auto globalPosition = e->globalPos();
|
||||
if (!_lastMousePosition) {
|
||||
_lastMousePosition = globalPosition;
|
||||
|
@ -857,7 +951,7 @@ void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
|
|||
selectByMouse(globalPosition);
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::clearSel(bool hidden) {
|
||||
void FieldAutocomplete::Inner::clearSel(bool hidden) {
|
||||
_overDelete = false;
|
||||
_mouseSelection = false;
|
||||
_lastMousePosition = std::nullopt;
|
||||
|
@ -868,7 +962,7 @@ void FieldAutocompleteInner::clearSel(bool hidden) {
|
|||
}
|
||||
}
|
||||
|
||||
bool FieldAutocompleteInner::moveSel(int key) {
|
||||
bool FieldAutocomplete::Inner::moveSel(int key) {
|
||||
_mouseSelection = false;
|
||||
_lastMousePosition = std::nullopt;
|
||||
|
||||
|
@ -903,12 +997,12 @@ bool FieldAutocompleteInner::moveSel(int key) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FieldAutocompleteInner::chooseSelected(
|
||||
bool FieldAutocomplete::Inner::chooseSelected(
|
||||
FieldAutocomplete::ChooseMethod method) const {
|
||||
return chooseAtIndex(method, _sel);
|
||||
}
|
||||
|
||||
bool FieldAutocompleteInner::chooseAtIndex(
|
||||
bool FieldAutocomplete::Inner::chooseAtIndex(
|
||||
FieldAutocomplete::ChooseMethod method,
|
||||
int index,
|
||||
Api::SendOptions options) const {
|
||||
|
@ -955,11 +1049,11 @@ bool FieldAutocompleteInner::chooseAtIndex(
|
|||
return false;
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
|
||||
void FieldAutocomplete::Inner::setRecentInlineBotsInRows(int32 bots) {
|
||||
_recentInlineBotsInRows = bots;
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
|
||||
void FieldAutocomplete::Inner::mousePressEvent(QMouseEvent *e) {
|
||||
selectByMouse(e->globalPos());
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) {
|
||||
|
@ -999,7 +1093,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
|
|||
}
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
void FieldAutocomplete::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_previewTimer.cancel();
|
||||
|
||||
int32 pressed = _down;
|
||||
|
@ -1017,7 +1111,7 @@ void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
|
|||
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
if (_sel < 0 || _srows->empty() || _down >= 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -1040,11 +1134,11 @@ void FieldAutocompleteInner::contextMenuEvent(QContextMenuEvent *e) {
|
|||
}
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::enterEventHook(QEvent *e) {
|
||||
void FieldAutocomplete::Inner::enterEventHook(QEvent *e) {
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
|
||||
void FieldAutocomplete::Inner::leaveEventHook(QEvent *e) {
|
||||
setMouseTracking(false);
|
||||
if (_mouseSelection) {
|
||||
setSel(-1);
|
||||
|
@ -1053,7 +1147,7 @@ void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
|
|||
}
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::updateSelectedRow() {
|
||||
void FieldAutocomplete::Inner::updateSelectedRow() {
|
||||
if (_sel >= 0) {
|
||||
if (_srows->empty()) {
|
||||
update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
|
||||
|
@ -1064,7 +1158,7 @@ void FieldAutocompleteInner::updateSelectedRow() {
|
|||
}
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::setSel(int sel, bool scroll) {
|
||||
void FieldAutocomplete::Inner::setSel(int sel, bool scroll) {
|
||||
updateSelectedRow();
|
||||
_sel = sel;
|
||||
updateSelectedRow();
|
||||
|
@ -1084,13 +1178,13 @@ void FieldAutocompleteInner::setSel(int sel, bool scroll) {
|
|||
}
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::rowsUpdated() {
|
||||
void FieldAutocomplete::Inner::rowsUpdated() {
|
||||
if (_srows->empty()) {
|
||||
_stickersLifetime.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
auto FieldAutocompleteInner::getLottieRenderer()
|
||||
auto FieldAutocomplete::Inner::getLottieRenderer()
|
||||
-> std::shared_ptr<Lottie::FrameRenderer> {
|
||||
if (auto result = _lottieRenderer.lock()) {
|
||||
return result;
|
||||
|
@ -1100,7 +1194,7 @@ auto FieldAutocompleteInner::getLottieRenderer()
|
|||
return result;
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
|
||||
void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
|
||||
const auto document = suggestion.document;
|
||||
suggestion.animated = ChatHelpers::LottiePlayerFromDocument(
|
||||
suggestion.documentMedia.get(),
|
||||
|
@ -1115,13 +1209,13 @@ void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
|
|||
}, _stickersLifetime);
|
||||
}
|
||||
|
||||
QSize FieldAutocompleteInner::stickerBoundingBox() const {
|
||||
QSize FieldAutocomplete::Inner::stickerBoundingBox() const {
|
||||
return QSize(
|
||||
st::stickerPanSize.width() - st::buttonRadius * 2,
|
||||
st::stickerPanSize.height() - st::buttonRadius * 2);
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::repaintSticker(
|
||||
void FieldAutocomplete::Inner::repaintSticker(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto i = ranges::find(
|
||||
*_srows,
|
||||
|
@ -1140,7 +1234,7 @@ void FieldAutocompleteInner::repaintSticker(
|
|||
st::stickerPanSize.height());
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
|
||||
void FieldAutocomplete::Inner::selectByMouse(QPoint globalPosition) {
|
||||
_mouseSelection = true;
|
||||
_lastMousePosition = globalPosition;
|
||||
const auto mouse = mapFromGlobal(globalPosition);
|
||||
|
@ -1186,7 +1280,7 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
|
|||
}
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::onParentGeometryChanged() {
|
||||
void FieldAutocomplete::Inner::onParentGeometryChanged() {
|
||||
const auto globalPosition = QCursor::pos();
|
||||
if (rect().contains(mapFromGlobal(globalPosition))) {
|
||||
setMouseTracking(true);
|
||||
|
@ -1196,7 +1290,7 @@ void FieldAutocompleteInner::onParentGeometryChanged() {
|
|||
}
|
||||
}
|
||||
|
||||
void FieldAutocompleteInner::showPreview() {
|
||||
void FieldAutocomplete::Inner::showPreview() {
|
||||
if (_down >= 0 && _down < _srows->size()) {
|
||||
if (const auto w = App::wnd()) {
|
||||
w->showMediaPreview(
|
||||
|
@ -1207,29 +1301,27 @@ void FieldAutocompleteInner::showPreview() {
|
|||
}
|
||||
}
|
||||
|
||||
auto FieldAutocompleteInner::mentionChosen() const
|
||||
auto FieldAutocomplete::Inner::mentionChosen() const
|
||||
-> rpl::producer<FieldAutocomplete::MentionChosen> {
|
||||
return _mentionChosen.events();
|
||||
}
|
||||
|
||||
auto FieldAutocompleteInner::hashtagChosen() const
|
||||
auto FieldAutocomplete::Inner::hashtagChosen() const
|
||||
-> rpl::producer<FieldAutocomplete::HashtagChosen> {
|
||||
return _hashtagChosen.events();
|
||||
}
|
||||
|
||||
auto FieldAutocompleteInner::botCommandChosen() const
|
||||
auto FieldAutocomplete::Inner::botCommandChosen() const
|
||||
-> rpl::producer<FieldAutocomplete::BotCommandChosen> {
|
||||
return _botCommandChosen.events();
|
||||
}
|
||||
|
||||
auto FieldAutocompleteInner::stickerChosen() const
|
||||
auto FieldAutocomplete::Inner::stickerChosen() const
|
||||
-> rpl::producer<FieldAutocomplete::StickerChosen> {
|
||||
return _stickerChosen.events();
|
||||
}
|
||||
|
||||
auto FieldAutocompleteInner::scrollToRequested() const
|
||||
auto FieldAutocomplete::Inner::scrollToRequested() const
|
||||
-> rpl::producer<ScrollTo> {
|
||||
return _scrollToRequested.events();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Ui {
|
||||
class PopupMenu;
|
||||
class ScrollArea;
|
||||
class InputField;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Lottie {
|
||||
|
@ -32,42 +33,15 @@ class DocumentMedia;
|
|||
class CloudImageView;
|
||||
} // namespace Data
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct StickerSuggestion {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
std::unique_ptr<Lottie::SinglePlayer> animated;
|
||||
};
|
||||
|
||||
struct MentionRow {
|
||||
not_null<UserData*> user;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
|
||||
struct BotCommandRow {
|
||||
not_null<UserData*> user;
|
||||
not_null<const BotCommand*> command;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
|
||||
using HashtagRows = std::vector<QString>;
|
||||
using BotCommandRows = std::vector<BotCommandRow>;
|
||||
using StickerRows = std::vector<StickerSuggestion>;
|
||||
using MentionRows = std::vector<MentionRow>;
|
||||
|
||||
class FieldAutocompleteInner;
|
||||
|
||||
} // namespace internal
|
||||
|
||||
class FieldAutocomplete final : public Ui::RpWidget {
|
||||
|
||||
public:
|
||||
FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
~FieldAutocomplete();
|
||||
|
||||
[[nodiscard]] not_null<Window::SessionController*> controller() const;
|
||||
|
||||
bool clearFilteredBotCommands();
|
||||
void showFiltered(
|
||||
not_null<PeerData*> peer,
|
||||
|
@ -140,29 +114,54 @@ protected:
|
|||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
class Inner;
|
||||
friend class Inner;
|
||||
|
||||
struct StickerSuggestion {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
std::unique_ptr<Lottie::SinglePlayer> animated;
|
||||
};
|
||||
|
||||
struct MentionRow {
|
||||
not_null<UserData*> user;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
|
||||
struct BotCommandRow {
|
||||
not_null<UserData*> user;
|
||||
not_null<const BotCommand*> command;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
|
||||
using HashtagRows = std::vector<QString>;
|
||||
using BotCommandRows = std::vector<BotCommandRow>;
|
||||
using StickerRows = std::vector<StickerSuggestion>;
|
||||
using MentionRows = std::vector<MentionRow>;
|
||||
|
||||
void animationCallback();
|
||||
void hideFinish();
|
||||
|
||||
void updateFiltered(bool resetScroll = false);
|
||||
void recount(bool resetScroll = false);
|
||||
internal::StickerRows getStickerSuggestions();
|
||||
StickerRows getStickerSuggestions();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
QPixmap _cache;
|
||||
internal::MentionRows _mrows;
|
||||
internal::HashtagRows _hrows;
|
||||
internal::BotCommandRows _brows;
|
||||
internal::StickerRows _srows;
|
||||
MentionRows _mrows;
|
||||
HashtagRows _hrows;
|
||||
BotCommandRows _brows;
|
||||
StickerRows _srows;
|
||||
|
||||
void rowsUpdated(
|
||||
internal::MentionRows &&mrows,
|
||||
internal::HashtagRows &&hrows,
|
||||
internal::BotCommandRows &&brows,
|
||||
internal::StickerRows &&srows,
|
||||
MentionRows &&mrows,
|
||||
HashtagRows &&hrows,
|
||||
BotCommandRows &&brows,
|
||||
StickerRows &&srows,
|
||||
bool resetScroll);
|
||||
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
QPointer<internal::FieldAutocompleteInner> _inner;
|
||||
QPointer<Inner> _inner;
|
||||
|
||||
ChatData *_chat = nullptr;
|
||||
UserData *_user = nullptr;
|
||||
|
@ -186,100 +185,4 @@ private:
|
|||
|
||||
Fn<bool(int)> _moderateKeyActivateCallback;
|
||||
|
||||
friend class internal::FieldAutocompleteInner;
|
||||
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
class FieldAutocompleteInner final
|
||||
: public Ui::RpWidget
|
||||
, private base::Subscriber {
|
||||
|
||||
public:
|
||||
struct ScrollTo {
|
||||
int top;
|
||||
int bottom;
|
||||
};
|
||||
|
||||
FieldAutocompleteInner(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
not_null<HashtagRows*> hrows,
|
||||
not_null<BotCommandRows*> brows,
|
||||
not_null<StickerRows*> srows);
|
||||
|
||||
void clearSel(bool hidden = false);
|
||||
bool moveSel(int key);
|
||||
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
|
||||
bool chooseAtIndex(
|
||||
FieldAutocomplete::ChooseMethod method,
|
||||
int index,
|
||||
Api::SendOptions options = Api::SendOptions()) const;
|
||||
|
||||
void setRecentInlineBotsInRows(int32 bots);
|
||||
void rowsUpdated();
|
||||
|
||||
rpl::producer<FieldAutocomplete::MentionChosen> mentionChosen() const;
|
||||
rpl::producer<FieldAutocomplete::HashtagChosen> hashtagChosen() const;
|
||||
rpl::producer<FieldAutocomplete::BotCommandChosen>
|
||||
botCommandChosen() const;
|
||||
rpl::producer<FieldAutocomplete::StickerChosen> stickerChosen() const;
|
||||
rpl::producer<ScrollTo> scrollToRequested() const;
|
||||
|
||||
void onParentGeometryChanged();
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void enterEventHook(QEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
void updateSelectedRow();
|
||||
void setSel(int sel, bool scroll = false);
|
||||
void showPreview();
|
||||
void selectByMouse(QPoint global);
|
||||
|
||||
QSize stickerBoundingBox() const;
|
||||
void setupLottie(StickerSuggestion &suggestion);
|
||||
void repaintSticker(not_null<DocumentData*> document);
|
||||
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<FieldAutocomplete*> _parent;
|
||||
const not_null<MentionRows*> _mrows;
|
||||
const not_null<HashtagRows*> _hrows;
|
||||
const not_null<BotCommandRows*> _brows;
|
||||
const not_null<StickerRows*> _srows;
|
||||
rpl::lifetime _stickersLifetime;
|
||||
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
int _stickersPerRow = 1;
|
||||
int _recentInlineBotsInRows = 0;
|
||||
int _sel = -1;
|
||||
int _down = -1;
|
||||
std::optional<QPoint> _lastMousePosition;
|
||||
bool _mouseSelection = false;
|
||||
|
||||
bool _overDelete = false;
|
||||
|
||||
bool _previewShown = false;
|
||||
|
||||
rpl::event_stream<FieldAutocomplete::MentionChosen> _mentionChosen;
|
||||
rpl::event_stream<FieldAutocomplete::HashtagChosen> _hashtagChosen;
|
||||
rpl::event_stream<FieldAutocomplete::BotCommandChosen> _botCommandChosen;
|
||||
rpl::event_stream<FieldAutocomplete::StickerChosen> _stickerChosen;
|
||||
rpl::event_stream<ScrollTo> _scrollToRequested;
|
||||
|
||||
base::Timer _previewTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
|
|
@ -286,7 +286,6 @@ HistoryWidget::HistoryWidget(
|
|||
_unreadMentions->installEventFilter(this);
|
||||
|
||||
InitMessageField(controller, _field);
|
||||
_fieldAutocomplete->hide();
|
||||
|
||||
_fieldAutocomplete->mentionChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
|
||||
|
@ -1216,6 +1215,9 @@ void HistoryWidget::orderWidgets() {
|
|||
_pinnedBar->raise();
|
||||
}
|
||||
_topShadow->raise();
|
||||
if (_fieldAutocomplete) {
|
||||
_fieldAutocomplete->raise();
|
||||
}
|
||||
if (_membersDropdown) {
|
||||
_membersDropdown->raise();
|
||||
}
|
||||
|
|
|
@ -15,12 +15,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_section.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "facades.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "history/history.h"
|
||||
|
@ -515,6 +519,9 @@ ComposeControls::ComposeControls(
|
|||
st::historyComposeField,
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_message_ph()))
|
||||
, _autocomplete(std::make_unique<FieldAutocomplete>(
|
||||
parent,
|
||||
window))
|
||||
, _header(std::make_unique<FieldHeader>(
|
||||
_wrap.get(),
|
||||
&_window->session().data()))
|
||||
|
@ -565,6 +572,12 @@ void ComposeControls::resizeToWidth(int width) {
|
|||
updateHeight();
|
||||
}
|
||||
|
||||
void ComposeControls::setAutocompleteBoundingRect(QRect rect) {
|
||||
if (_autocomplete) {
|
||||
_autocomplete->setBoundings(rect);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> ComposeControls::height() const {
|
||||
using namespace rpl::mappers;
|
||||
return rpl::conditional(
|
||||
|
@ -619,6 +632,10 @@ rpl::producer<VoiceToSend> ComposeControls::sendVoiceRequests() const {
|
|||
return _voiceRecordBar->sendVoiceRequests();
|
||||
}
|
||||
|
||||
rpl::producer<QString> ComposeControls::sendCommandRequests() const {
|
||||
return _sendCommandRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<MessageToEdit> ComposeControls::editRequests() const {
|
||||
auto toValue = rpl::map([=] { return _header->queryToEdit(); });
|
||||
auto filter = rpl::filter([=] {
|
||||
|
@ -681,6 +698,21 @@ void ComposeControls::showFinished() {
|
|||
_voiceRecordBar->orderControls();
|
||||
}
|
||||
|
||||
void ComposeControls::raisePanels() {
|
||||
if (_autocomplete) {
|
||||
_autocomplete->raise();
|
||||
}
|
||||
if (_inlineResults) {
|
||||
_inlineResults->raise();
|
||||
}
|
||||
if (_tabbedPanel) {
|
||||
_tabbedPanel->raise();
|
||||
}
|
||||
if (_raiseEmojiSuggestions) {
|
||||
_raiseEmojiSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
void ComposeControls::showForGrab() {
|
||||
showFinished();
|
||||
}
|
||||
|
@ -708,7 +740,9 @@ void ComposeControls::setText(const TextWithTags &textWithTags) {
|
|||
}
|
||||
|
||||
void ComposeControls::hidePanelsAnimated() {
|
||||
//_fieldAutocomplete->hideAnimated();
|
||||
if (_autocomplete) {
|
||||
_autocomplete->hideAnimated();
|
||||
}
|
||||
if (_tabbedPanel) {
|
||||
_tabbedPanel->hideAnimated();
|
||||
}
|
||||
|
@ -717,6 +751,36 @@ void ComposeControls::hidePanelsAnimated() {
|
|||
}
|
||||
}
|
||||
|
||||
void ComposeControls::checkAutocomplete() {
|
||||
if (!_history) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto peer = _history->peer;
|
||||
const auto isInlineBot = false;// _inlineBot && !_inlineLookingUpBot;
|
||||
const auto autocomplete = isInlineBot
|
||||
? AutocompleteQuery()
|
||||
: ParseMentionHashtagBotCommandQuery(_field);
|
||||
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::init() {
|
||||
initField();
|
||||
initTabbedSelector();
|
||||
|
@ -817,11 +881,12 @@ void ComposeControls::initField() {
|
|||
_field->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
//Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); });
|
||||
Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); });
|
||||
//Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); });
|
||||
Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); });
|
||||
Ui::Connect(_field, &Ui::InputField::resized, [=] { updateHeight(); });
|
||||
//Ui::Connect(_field, &Ui::InputField::focused, [=] { fieldFocused(); });
|
||||
Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); });
|
||||
InitMessageField(_window, _field);
|
||||
initAutocomplete();
|
||||
const auto suggestions = Ui::Emoji::SuggestionsController::Init(
|
||||
_parent,
|
||||
_field,
|
||||
|
@ -830,6 +895,112 @@ void ComposeControls::initField() {
|
|||
InitSpellchecker(_window, _field);
|
||||
}
|
||||
|
||||
void ComposeControls::initAutocomplete() {
|
||||
const auto insertHashtagOrBotCommand = [=](
|
||||
const QString &string,
|
||||
FieldAutocomplete::ChooseMethod method) {
|
||||
// Send bot command at once, if it was not inserted by pressing Tab.
|
||||
if (string.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) {
|
||||
_sendCommandRequests.fire_copy(string);
|
||||
setText(
|
||||
_field->getTextWithTagsPart(_field->textCursor().position()));
|
||||
} else {
|
||||
_field->insertTag(string);
|
||||
}
|
||||
};
|
||||
const auto insertMention = [=](not_null<UserData*> user) {
|
||||
auto replacement = QString();
|
||||
auto entityTag = QString();
|
||||
if (user->username.isEmpty()) {
|
||||
_field->insertTag(
|
||||
user->firstName.isEmpty() ? user->name : user->firstName,
|
||||
PrepareMentionTag(user));
|
||||
} else {
|
||||
_field->insertTag('@' + user->username);
|
||||
}
|
||||
};
|
||||
|
||||
_autocomplete->mentionChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
|
||||
insertMention(data.user);
|
||||
}, _autocomplete->lifetime());
|
||||
|
||||
_autocomplete->hashtagChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) {
|
||||
insertHashtagOrBotCommand(data.hashtag, data.method);
|
||||
}, _autocomplete->lifetime());
|
||||
|
||||
_autocomplete->botCommandChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) {
|
||||
insertHashtagOrBotCommand(data.command, data.method);
|
||||
}, _autocomplete->lifetime());
|
||||
|
||||
_autocomplete->stickerChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::StickerChosen data) {
|
||||
setText({});
|
||||
//_saveDraftText = true;
|
||||
//_saveDraftStart = crl::now();
|
||||
//onDraftSave();
|
||||
//onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
|
||||
_fileChosen.fire(FileChosen{
|
||||
.document = data.sticker,
|
||||
.options = data.options,
|
||||
});
|
||||
}, _autocomplete->lifetime());
|
||||
|
||||
//_autocomplete->setModerateKeyActivateCallback([=](int key) {
|
||||
// return _keyboard->isHidden()
|
||||
// ? false
|
||||
// : _keyboard->moderateKeyActivate(key);
|
||||
//});
|
||||
|
||||
_field->rawTextEdit()->installEventFilter(_autocomplete.get());
|
||||
|
||||
_window->session().data().botCommandsChanges(
|
||||
) | rpl::filter([=](not_null<UserData*> user) {
|
||||
const auto peer = _history ? _history->peer.get() : nullptr;
|
||||
return peer && (peer == user || !peer->isUser());
|
||||
}) | rpl::start_with_next([=](not_null<UserData*> user) {
|
||||
if (_autocomplete->clearFilteredBotCommands()) {
|
||||
checkAutocomplete();
|
||||
}
|
||||
}, _autocomplete->lifetime());
|
||||
|
||||
_window->session().data().stickers().updated(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateStickersByEmoji();
|
||||
}, _autocomplete->lifetime());
|
||||
|
||||
QObject::connect(
|
||||
_field->rawTextEdit(),
|
||||
&QTextEdit::cursorPositionChanged,
|
||||
_autocomplete.get(),
|
||||
[=] { checkAutocomplete(); },
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ComposeControls::updateStickersByEmoji() {
|
||||
if (!_history) {
|
||||
return;
|
||||
}
|
||||
const auto emoji = [&] {
|
||||
const auto errorForStickers = Data::RestrictionError(
|
||||
_history->peer,
|
||||
ChatRestriction::f_send_stickers);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return EmojiPtr(nullptr);
|
||||
}();
|
||||
_autocomplete->showStickers(emoji);
|
||||
}
|
||||
|
||||
void ComposeControls::fieldChanged() {
|
||||
if (/*!_inlineBot
|
||||
&& */!_header->isEditingMessage()
|
||||
|
@ -840,6 +1011,15 @@ void ComposeControls::fieldChanged() {
|
|||
if (showRecordButton()) {
|
||||
//_previewCancelled = false;
|
||||
}
|
||||
InvokeQueued(_autocomplete.get(), [=] {
|
||||
updateStickersByEmoji();
|
||||
});
|
||||
}
|
||||
|
||||
void ComposeControls::fieldTabbed() {
|
||||
if (!_autocomplete->isHidden()) {
|
||||
_autocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<SendActionUpdate> ComposeControls::sendActionUpdates() const {
|
||||
|
@ -1136,10 +1316,16 @@ void ComposeControls::updateHeight() {
|
|||
void ComposeControls::editMessage(FullMsgId id) {
|
||||
cancelEditMessage();
|
||||
_header->editMessage(id);
|
||||
if (_autocomplete) {
|
||||
InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); });
|
||||
}
|
||||
}
|
||||
|
||||
void ComposeControls::cancelEditMessage() {
|
||||
_header->editMessage({});
|
||||
if (_autocomplete) {
|
||||
InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); });
|
||||
}
|
||||
}
|
||||
|
||||
void ComposeControls::replyToMessage(FullMsgId id) {
|
||||
|
@ -1151,6 +1337,20 @@ void ComposeControls::cancelReplyMessage() {
|
|||
_header->replyToMessage({});
|
||||
}
|
||||
|
||||
bool ComposeControls::handleCancelRequest() {
|
||||
if (isEditingMessage()) {
|
||||
cancelEditMessage();
|
||||
return true;
|
||||
} else if (_autocomplete && !_autocomplete->isHidden()) {
|
||||
_autocomplete->hideAnimated();
|
||||
return true;
|
||||
} else if (replyingToMessage()) {
|
||||
cancelReplyMessage();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ComposeControls::initWebpageProcess() {
|
||||
Expects(_history);
|
||||
const auto peer = _history->peer;
|
||||
|
@ -1287,7 +1487,10 @@ void ComposeControls::initWebpageProcess() {
|
|||
Data::PeerUpdate::Flag::Rights
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
return (update.peer.get() == peer);
|
||||
}) | rpl::start_with_next(checkPreview, lifetime);
|
||||
}) | rpl::start_with_next([=] {
|
||||
checkPreview();
|
||||
updateStickersByEmoji();
|
||||
}, lifetime);
|
||||
|
||||
_window->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/tabbed_selector.h"
|
||||
|
||||
class History;
|
||||
class FieldAutocomplete;
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
|
@ -89,6 +90,7 @@ public:
|
|||
|
||||
void move(int x, int y);
|
||||
void resizeToWidth(int width);
|
||||
void setAutocompleteBoundingRect(QRect rect);
|
||||
[[nodiscard]] rpl::producer<int> height() const;
|
||||
[[nodiscard]] int heightCurrent() const;
|
||||
|
||||
|
@ -96,6 +98,7 @@ public:
|
|||
[[nodiscard]] rpl::producer<> cancelRequests() const;
|
||||
[[nodiscard]] rpl::producer<> sendRequests() const;
|
||||
[[nodiscard]] rpl::producer<VoiceToSend> sendVoiceRequests() const;
|
||||
[[nodiscard]] rpl::producer<QString> sendCommandRequests() const;
|
||||
[[nodiscard]] rpl::producer<MessageToEdit> editRequests() const;
|
||||
[[nodiscard]] rpl::producer<> attachRequests() const;
|
||||
[[nodiscard]] rpl::producer<FileChosen> fileChosen() const;
|
||||
|
@ -122,6 +125,7 @@ public:
|
|||
void showForGrab();
|
||||
void showStarted();
|
||||
void showFinished();
|
||||
void raisePanels();
|
||||
|
||||
void editMessage(FullMsgId id);
|
||||
void cancelEditMessage();
|
||||
|
@ -129,6 +133,8 @@ public:
|
|||
void replyToMessage(FullMsgId id);
|
||||
void cancelReplyMessage();
|
||||
|
||||
bool handleCancelRequest();
|
||||
|
||||
[[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
|
||||
[[nodiscard]] WebPageId webPageId() const;
|
||||
void setText(const TextWithTags &text);
|
||||
|
@ -154,6 +160,7 @@ private:
|
|||
void initWebpageProcess();
|
||||
void initWriteRestriction();
|
||||
void initVoiceRecordBar();
|
||||
void initAutocomplete();
|
||||
void updateSendButtonType();
|
||||
void updateHeight();
|
||||
void updateWrappingVisibility();
|
||||
|
@ -163,9 +170,12 @@ private:
|
|||
void paintBackground(QRect clip);
|
||||
|
||||
void orderControls();
|
||||
void checkAutocomplete();
|
||||
void updateStickersByEmoji();
|
||||
|
||||
void escape();
|
||||
void fieldChanged();
|
||||
void fieldTabbed();
|
||||
void toggleTabbedSelectorMode();
|
||||
void createTabbedPanel();
|
||||
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
|
||||
|
@ -194,6 +204,7 @@ private:
|
|||
const not_null<Ui::InputField*> _field;
|
||||
std::unique_ptr<InlineBots::Layout::Widget> _inlineResults;
|
||||
std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
|
||||
std::unique_ptr<FieldAutocomplete> _autocomplete;
|
||||
|
||||
friend class FieldHeader;
|
||||
const std::unique_ptr<FieldHeader> _header;
|
||||
|
@ -204,6 +215,7 @@ private:
|
|||
rpl::event_stream<PhotoChosen> _photoChosen;
|
||||
rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen;
|
||||
rpl::event_stream<SendActionUpdate> _sendActionUpdates;
|
||||
rpl::event_stream<QString> _sendCommandRequests;
|
||||
|
||||
TextWithTags _localSavedText;
|
||||
TextUpdateEvents _textUpdateEvents;
|
||||
|
|
|
@ -253,6 +253,7 @@ RepliesWidget::RepliesWidget(
|
|||
|
||||
setupScrollDownButton();
|
||||
setupComposeControls();
|
||||
orderWidgets();
|
||||
}
|
||||
|
||||
RepliesWidget::~RepliesWidget() {
|
||||
|
@ -263,6 +264,17 @@ RepliesWidget::~RepliesWidget() {
|
|||
_history->owner().repliesSendActionPainterRemoved(_history, _rootId);
|
||||
}
|
||||
|
||||
void RepliesWidget::orderWidgets() {
|
||||
if (_topBar) {
|
||||
_topBar->raise();
|
||||
}
|
||||
if (_rootView) {
|
||||
_rootView->raise();
|
||||
}
|
||||
_topBarShadow->raise();
|
||||
_composeControls->raisePanels();
|
||||
}
|
||||
|
||||
void RepliesWidget::sendReadTillRequest() {
|
||||
if (!_root) {
|
||||
_readRequestPending = true;
|
||||
|
@ -428,6 +440,18 @@ void RepliesWidget::setupComposeControls() {
|
|||
sendVoice(data.bytes, data.waveform, data.duration);
|
||||
}, lifetime());
|
||||
|
||||
_composeControls->sendCommandRequests(
|
||||
) | rpl::start_with_next([=](const QString &command) {
|
||||
if (showSlowmodeError()) {
|
||||
return;
|
||||
}
|
||||
auto message = ApiWrap::MessageToSend(_history);
|
||||
message.textWithTags = { command };
|
||||
message.action.replyTo = replyToId();
|
||||
session().api().sendMessage(std::move(message));
|
||||
finishSending();
|
||||
}, lifetime());
|
||||
|
||||
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
|
||||
_composeControls->editRequests(
|
||||
) | rpl::start_with_next([=](auto data) {
|
||||
|
@ -1474,6 +1498,7 @@ void RepliesWidget::updateControlsGeometry() {
|
|||
updateInnerVisibleArea();
|
||||
}
|
||||
_composeControls->move(0, bottom - controlsHeight);
|
||||
_composeControls->setAutocompleteBoundingRect(_scroll->geometry());
|
||||
|
||||
updateScrollDownPosition();
|
||||
}
|
||||
|
@ -1598,12 +1623,7 @@ void RepliesWidget::listCancelRequest() {
|
|||
if (_inner && !_inner->getSelectedItems().empty()) {
|
||||
clearSelected();
|
||||
return;
|
||||
}
|
||||
if (_composeControls->isEditingMessage()) {
|
||||
_composeControls->cancelEditMessage();
|
||||
return;
|
||||
} else if (_composeControls->replyingToMessage()) {
|
||||
_composeControls->cancelReplyMessage();
|
||||
} else if (_composeControls->handleCancelRequest()) {
|
||||
return;
|
||||
}
|
||||
controller()->showBackFromStack();
|
||||
|
|
|
@ -186,6 +186,7 @@ private:
|
|||
[[nodiscard]] MsgId replyToId() const;
|
||||
[[nodiscard]] HistoryItem *lookupRoot() const;
|
||||
[[nodiscard]] bool computeAreComments() const;
|
||||
void orderWidgets();
|
||||
|
||||
void pushReplyReturn(not_null<HistoryItem*> item);
|
||||
void computeCurrentReplyReturn();
|
||||
|
|
|
@ -180,6 +180,19 @@ void ScheduledWidget::setupComposeControls() {
|
|||
sendVoice(data.bytes, data.waveform, data.duration);
|
||||
}, lifetime());
|
||||
|
||||
_composeControls->sendCommandRequests(
|
||||
) | rpl::start_with_next([=](const QString &command) {
|
||||
const auto callback = [=](Api::SendOptions options) {
|
||||
auto message = ApiWrap::MessageToSend(_history);
|
||||
message.textWithTags = { command };
|
||||
message.action.options = options;
|
||||
session().api().sendMessage(std::move(message));
|
||||
};
|
||||
Ui::show(
|
||||
PrepareScheduleBox(this, sendMenuType(), callback),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}, lifetime());
|
||||
|
||||
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
|
||||
_composeControls->editRequests(
|
||||
) | rpl::start_with_next([=](auto data) {
|
||||
|
@ -979,6 +992,7 @@ void ScheduledWidget::updateControlsGeometry() {
|
|||
updateInnerVisibleArea();
|
||||
}
|
||||
_composeControls->move(0, bottom - controlsHeight);
|
||||
_composeControls->setAutocompleteBoundingRect(_scroll->geometry());
|
||||
|
||||
updateScrollDownPosition();
|
||||
}
|
||||
|
@ -1057,9 +1071,7 @@ void ScheduledWidget::listCancelRequest() {
|
|||
if (_inner && !_inner->getSelectedItems().empty()) {
|
||||
clearSelected();
|
||||
return;
|
||||
}
|
||||
if (_composeControls->isEditingMessage()) {
|
||||
_composeControls->cancelEditMessage();
|
||||
} else if (_composeControls->handleCancelRequest()) {
|
||||
return;
|
||||
}
|
||||
controller()->showBackFromStack();
|
||||
|
|
|
@ -35,7 +35,7 @@ struct Contact {
|
|||
QString lastName;
|
||||
};
|
||||
|
||||
class Autocomplete : public Ui::RpWidget {
|
||||
class Autocomplete final : public Ui::RpWidget {
|
||||
public:
|
||||
Autocomplete(QWidget *parent, not_null<Main::Session*> session);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue