Added ability to select message for reply with Ctrl+Up/Down in sections.

This commit is contained in:
23rd 2021-01-31 23:49:25 +03:00 committed by John Preston
parent 0b98cfbfec
commit f1236edf5b
6 changed files with 122 additions and 0 deletions

View file

@ -40,4 +40,13 @@ struct SetHistoryArgs {
rpl::producer<std::optional<QString>> writeRestriction; rpl::producer<std::optional<QString>> writeRestriction;
}; };
struct ReplyNextRequest {
enum class Direction {
Next,
Previous,
};
const FullMsgId replyId;
const Direction direction;
};
} // namespace HistoryView::Controls } // namespace HistoryView::Controls

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/controls/history_view_compose_controls.h" #include "history/view/controls/history_view_compose_controls.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "base/platform/base_platform_info.h"
#include "base/qt_signal_producer.h" #include "base/qt_signal_producer.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
@ -64,6 +65,11 @@ constexpr auto kMouseEvents = {
QEvent::MouseButtonRelease QEvent::MouseButtonRelease
}; };
constexpr auto kCommonModifiers = 0
| Qt::ShiftModifier
| Qt::MetaModifier
| Qt::ControlModifier;
using FileChosen = ComposeControls::FileChosen; using FileChosen = ComposeControls::FileChosen;
using PhotoChosen = ComposeControls::PhotoChosen; using PhotoChosen = ComposeControls::PhotoChosen;
using MessageToEdit = ComposeControls::MessageToEdit; using MessageToEdit = ComposeControls::MessageToEdit;
@ -722,6 +728,11 @@ auto ComposeControls::editLastMessageRequests() const
return _editLastMessageRequests.events(); return _editLastMessageRequests.events();
} }
auto ComposeControls::replyNextRequests() const
-> rpl::producer<ReplyNextRequest> {
return _replyNextRequests.events();
}
auto ComposeControls::sendContentRequests(SendRequestType requestType) const { auto ComposeControls::sendContentRequests(SendRequestType requestType) const {
auto filter = rpl::filter([=] { auto filter = rpl::filter([=] {
const auto type = (_mode == Mode::Normal) const auto type = (_mode == Mode::Normal)
@ -1077,6 +1088,37 @@ void ComposeControls::initKeyHandler() {
_scrollKeyEvents.fire(std::move(keyEvent)); _scrollKeyEvents.fire(std::move(keyEvent));
} }
}, _wrap->lifetime()); }, _wrap->lifetime());
base::install_event_filter(_wrap.get(), _field, [=](not_null<QEvent*> e) {
using Result = base::EventFilterResult;
if (e->type() != QEvent::KeyPress) {
return Result::Continue;
}
const auto k = static_cast<QKeyEvent*>(e.get());
if ((k->modifiers() & kCommonModifiers) == Qt::ControlModifier) {
const auto isUp = (k->key() == Qt::Key_Up);
const auto isDown = (k->key() == Qt::Key_Down);
if (isUp || isDown) {
if (Platform::IsMac()) {
// Cmd + Up is used instead of Home.
if ((isUp && (!_field->textCursor().atStart()))
// Cmd + Down is used instead of End.
|| (isDown && (!_field->textCursor().atEnd()))) {
return Result::Continue;
}
}
_replyNextRequests.fire({
.replyId = replyingToMessage(),
.direction = (isDown
? ReplyNextRequest::Direction::Next
: ReplyNextRequest::Direction::Previous)
});
return Result::Cancel;
}
}
return Result::Continue;
});
} }
void ComposeControls::initField() { void ComposeControls::initField() {

View file

@ -82,6 +82,7 @@ public:
using VoiceToSend = Controls::VoiceToSend; using VoiceToSend = Controls::VoiceToSend;
using SendActionUpdate = Controls::SendActionUpdate; using SendActionUpdate = Controls::SendActionUpdate;
using SetHistoryArgs = Controls::SetHistoryArgs; using SetHistoryArgs = Controls::SetHistoryArgs;
using ReplyNextRequest = Controls::ReplyNextRequest;
using FieldHistoryAction = Ui::InputField::HistoryAction; using FieldHistoryAction = Ui::InputField::HistoryAction;
enum class Mode { enum class Mode {
@ -125,6 +126,8 @@ public:
-> rpl::producer<not_null<QKeyEvent*>>; -> rpl::producer<not_null<QKeyEvent*>>;
[[nodiscard]] auto editLastMessageRequests() const [[nodiscard]] auto editLastMessageRequests() const
-> rpl::producer<not_null<QKeyEvent*>>; -> rpl::producer<not_null<QKeyEvent*>>;
[[nodiscard]] auto replyNextRequests() const
-> rpl::producer<ReplyNextRequest>;
using MimeDataHook = Fn<bool( using MimeDataHook = Fn<bool(
not_null<const QMimeData*> data, not_null<const QMimeData*> data,
@ -297,6 +300,7 @@ private:
rpl::event_stream<not_null<QKeyEvent*>> _scrollKeyEvents; rpl::event_stream<not_null<QKeyEvent*>> _scrollKeyEvents;
rpl::event_stream<not_null<QKeyEvent*>> _editLastMessageRequests; rpl::event_stream<not_null<QKeyEvent*>> _editLastMessageRequests;
rpl::event_stream<> _attachRequests; rpl::event_stream<> _attachRequests;
rpl::event_stream<ReplyNextRequest> _replyNextRequests;
TextUpdateEvents _textUpdateEvents = TextUpdateEvents() TextUpdateEvents _textUpdateEvents = TextUpdateEvents()
| TextUpdateEvent::SaveDraft | TextUpdateEvent::SaveDraft

View file

@ -525,6 +525,11 @@ void ListWidget::updateHighlightedMessage() {
_highlightedMessageId = FullMsgId(); _highlightedMessageId = FullMsgId();
} }
void ListWidget::clearHighlightedMessage() {
_highlightedMessageId = FullMsgId();
updateHighlightedMessage();
}
void ListWidget::checkUnreadBarCreation() { void ListWidget::checkUnreadBarCreation() {
if (!_bar.element) { if (!_bar.element) {
if (auto data = _delegate->listMessagesBar(_items); data.bar.element) { if (auto data = _delegate->listMessagesBar(_items); data.bar.element) {
@ -2759,6 +2764,49 @@ rpl::producer<FullMsgId> ListWidget::readMessageRequested() const {
return _requestedToReadMessage.events(); return _requestedToReadMessage.events();
} }
rpl::producer<FullMsgId> ListWidget::showMessageRequested() const {
return _requestedToShowMessage.events();
}
void ListWidget::replyNextMessage(FullMsgId fullId, bool next) {
const auto reply = [&](Element *view) {
if (view) {
const auto newFullId = view->data()->fullId();
replyToMessageRequestNotify(newFullId);
_requestedToShowMessage.fire_copy(newFullId);
} else {
replyToMessageRequestNotify(FullMsgId());
clearHighlightedMessage();
}
};
const auto replyFirst = [&] {
reply(next ? nullptr : _items.back().get());
};
if (!fullId) {
replyFirst();
return;
}
auto proj = [&](not_null<Element*> view) {
return view->data()->fullId() == fullId;
};
const auto &list = ranges::view::reverse(_items);
const auto it = ranges::find_if(list, std::move(proj));
if (it == end(list)) {
replyFirst();
return;
} else {
const auto nextIt = it + (next ? -1 : 1);
if (nextIt == end(list)) {
return;
} else if (next && (it == begin(list))) {
reply(nullptr);
} else {
reply(nextIt->get());
}
}
}
ListWidget::~ListWidget() = default; ListWidget::~ListWidget() = default;
void ConfirmDeleteSelectedItems(not_null<ListWidget*> widget) { void ConfirmDeleteSelectedItems(not_null<ListWidget*> widget) {

View file

@ -211,6 +211,8 @@ public:
[[nodiscard]] rpl::producer<FullMsgId> replyToMessageRequested() const; [[nodiscard]] rpl::producer<FullMsgId> replyToMessageRequested() const;
void replyToMessageRequestNotify(FullMsgId item); void replyToMessageRequestNotify(FullMsgId item);
[[nodiscard]] rpl::producer<FullMsgId> readMessageRequested() const; [[nodiscard]] rpl::producer<FullMsgId> readMessageRequested() const;
[[nodiscard]] rpl::producer<FullMsgId> showMessageRequested() const;
void replyNextMessage(FullMsgId fullId, bool next = true);
// ElementDelegate interface. // ElementDelegate interface.
Context elementContext() override; Context elementContext() override;
@ -448,6 +450,7 @@ private:
void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo); void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo);
void updateHighlightedMessage(); void updateHighlightedMessage();
void clearHighlightedMessage();
// This function finds all history items that are displayed and calls template method // This function finds all history items that are displayed and calls template method
// for each found message (in given direction) in the passed history with passed top offset. // for each found message (in given direction) in the passed history with passed top offset.
@ -555,6 +558,7 @@ private:
rpl::event_stream<FullMsgId> _requestedToEditMessage; rpl::event_stream<FullMsgId> _requestedToEditMessage;
rpl::event_stream<FullMsgId> _requestedToReplyToMessage; rpl::event_stream<FullMsgId> _requestedToReplyToMessage;
rpl::event_stream<FullMsgId> _requestedToReadMessage; rpl::event_stream<FullMsgId> _requestedToReadMessage;
rpl::event_stream<FullMsgId> _requestedToShowMessage;
rpl::lifetime _viewerLifetime; rpl::lifetime _viewerLifetime;

View file

@ -221,6 +221,13 @@ RepliesWidget::RepliesWidget(
replyToMessage(fullId); replyToMessage(fullId);
}, _inner->lifetime()); }, _inner->lifetime());
_inner->showMessageRequested(
) | rpl::start_with_next([=](auto fullId) {
if (const auto item = session().data().message(fullId)) {
showAtPosition(item->position());
}
}, _inner->lifetime());
_composeControls->sendActionUpdates( _composeControls->sendActionUpdates(
) | rpl::start_with_next([=](ComposeControls::SendActionUpdate &&data) { ) | rpl::start_with_next([=](ComposeControls::SendActionUpdate &&data) {
session().sendProgressManager().update( session().sendProgressManager().update(
@ -500,6 +507,14 @@ void RepliesWidget::setupComposeControls() {
} }
}, lifetime()); }, lifetime());
_composeControls->replyNextRequests(
) | rpl::start_with_next([=](ComposeControls::ReplyNextRequest &&data) {
using Direction = ComposeControls::ReplyNextRequest::Direction;
_inner->replyNextMessage(
data.replyId,
data.direction == Direction::Next);
}, lifetime());
_composeControls->setMimeDataHook([=]( _composeControls->setMimeDataHook([=](
not_null<const QMimeData*> data, not_null<const QMimeData*> data,
Ui::InputField::MimeAction action) { Ui::InputField::MimeAction action) {