Skip to content

Commit 734e649

Browse files
mwaddipclaude
andcommitted
feat: cycle through editable messages with Alt+Shift+Up/Down
Lets keyboard-only users step through their own editable messages without a mouse: - Alt+Shift+Up edits the most recent editable message, then walks to older ones on each press (stops at the oldest). - Alt+Shift+Down walks to newer ones and exits edit mode past the newest. - Jumping away from an edit with unsaved changes shows the existing "Cancel editing?" confirmation. Implemented in HistoryWidget alongside the existing Ctrl+Up/Down reply cycling, reusing History::lastEditableMessage and the displayed-message traversal. Ctrl+Shift+Up/Down was avoided because it is already bound to folder navigation. Main chat only; ComposeControls surfaces are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a835150 commit 734e649

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

Telegram/SourceFiles/history/history_widget.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ For license and copyright information please follow this link:
7474
#include "data/notify/data_notify_settings.h"
7575
#include "data/data_changes.h"
7676
#include "data/data_drafts.h"
77+
#include "data/data_groups.h"
7778
#include "data/data_session.h"
7879
#include "data/data_todo_list.h"
7980
#include "data/data_web_page.h"
@@ -223,6 +224,12 @@ constexpr auto kCommonModifiers = 0
223224
| Qt::ShiftModifier
224225
| Qt::MetaModifier
225226
| Qt::ControlModifier;
227+
228+
[[nodiscard]] bool IsEditCycleModifiers(Qt::KeyboardModifiers modifiers) {
229+
return (modifiers & (kCommonModifiers | Qt::AltModifier))
230+
== (Qt::AltModifier | Qt::ShiftModifier);
231+
}
232+
226233
const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
227234

228235
[[nodiscard]] rpl::producer<PeerData*> ActivePeerValue(
@@ -5857,6 +5864,12 @@ bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
58575864
#endif
58585865
return replyToNextMessage();
58595866
}
5867+
} else if (IsEditCycleModifiers(k->modifiers())) {
5868+
if (k->key() == Qt::Key_Up) {
5869+
return editPreviousMessage();
5870+
} else if (k->key() == Qt::Key_Down) {
5871+
return editNextMessage();
5872+
}
58605873
}
58615874
}
58625875
return RpWidget::eventFilter(obj, e);
@@ -8119,6 +8132,16 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
81198132
if (!replyToNextMessage()) {
81208133
e->ignore();
81218134
}
8135+
} else if (e->key() == Qt::Key_Up
8136+
&& IsEditCycleModifiers(e->modifiers())) {
8137+
if (!editPreviousMessage()) {
8138+
e->ignore();
8139+
}
8140+
} else if (e->key() == Qt::Key_Down
8141+
&& IsEditCycleModifiers(e->modifiers())) {
8142+
if (!editNextMessage()) {
8143+
e->ignore();
8144+
}
81228145
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
81238146
if (!_botStart->isHidden()) {
81248147
sendBotStartCommand();
@@ -8249,6 +8272,87 @@ bool HistoryWidget::replyToNextMessage() {
82498272
return false;
82508273
}
82518274

8275+
bool HistoryWidget::editPreviousMessage() {
8276+
if (!_history) {
8277+
return false;
8278+
}
8279+
const auto target = [&]() -> HistoryItem* {
8280+
if (!_editMsgId || !_replyEditMsg) {
8281+
return _history->lastEditableMessage();
8282+
}
8283+
const auto view = _replyEditMsg->mainView();
8284+
if (!view) {
8285+
return nullptr;
8286+
}
8287+
const auto now = base::unixtime::now();
8288+
auto previous = view->previousDisplayedInBlocks();
8289+
while (previous && !previous->data()->allowsEdit(now)) {
8290+
previous = previous->previousDisplayedInBlocks();
8291+
}
8292+
if (!previous) {
8293+
return nullptr;
8294+
}
8295+
return session().data().groups().findItemToEdit(previous->data());
8296+
}();
8297+
if (!target) {
8298+
return false;
8299+
}
8300+
const auto id = target->fullId();
8301+
confirmDiscardEdit(crl::guard(this, [=] {
8302+
if (const auto item = session().data().message(id)) {
8303+
editMessage(item, {});
8304+
}
8305+
}));
8306+
return true;
8307+
}
8308+
8309+
bool HistoryWidget::editNextMessage() {
8310+
if (!_history || !_editMsgId || !_replyEditMsg) {
8311+
return false;
8312+
}
8313+
const auto view = _replyEditMsg->mainView();
8314+
if (!view) {
8315+
return false;
8316+
}
8317+
const auto now = base::unixtime::now();
8318+
auto next = view->nextDisplayedInBlocks();
8319+
while (next && !next->data()->allowsEdit(now)) {
8320+
next = next->nextDisplayedInBlocks();
8321+
}
8322+
if (next) {
8323+
const auto id = session().data().groups().findItemToEdit(
8324+
next->data())->fullId();
8325+
confirmDiscardEdit(crl::guard(this, [=] {
8326+
if (const auto item = session().data().message(id)) {
8327+
editMessage(item, {});
8328+
}
8329+
}));
8330+
} else {
8331+
confirmDiscardEdit(crl::guard(this, [=] {
8332+
cancelEdit();
8333+
}));
8334+
}
8335+
return true;
8336+
}
8337+
8338+
void HistoryWidget::confirmDiscardEdit(Fn<void()> proceed) {
8339+
if (_editMsgId
8340+
&& _replyEditMsg
8341+
&& EditTextChanged(_replyEditMsg, _field->getTextWithTags())) {
8342+
controller()->show(Ui::MakeConfirmBox({
8343+
.text = tr::lng_cancel_edit_post_sure(),
8344+
.confirmed = crl::guard(this, [=](Fn<void()> &&close) {
8345+
proceed();
8346+
close();
8347+
}),
8348+
.confirmText = tr::lng_cancel_edit_post_yes(),
8349+
.cancelText = tr::lng_cancel_edit_post_no(),
8350+
}));
8351+
} else {
8352+
proceed();
8353+
}
8354+
}
8355+
82528356
bool HistoryWidget::showSlowmodeError() {
82538357
const auto text = [&] {
82548358
if (const auto left = _peer->slowmodeSecondsLeft()) {

Telegram/SourceFiles/history/history_widget.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,9 @@ class HistoryWidget final
570570
void cancelReplyAfterMediaSend(bool lastKeyboardUsed);
571571
bool replyToPreviousMessage();
572572
bool replyToNextMessage();
573+
bool editPreviousMessage();
574+
bool editNextMessage();
575+
void confirmDiscardEdit(Fn<void()> proceed);
573576
[[nodiscard]] bool showSlowmodeError();
574577

575578
void hideChildWidgets();

0 commit comments

Comments
 (0)