Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion src/qt/terminal_widget.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <QCloseEvent>
#include <QFontDatabase>
#include <QFontMetricsF>
#include <QInputMethodEvent>
#include <QKeyEvent>
#include <QResizeEvent>
#include <QShowEvent>
Expand All @@ -25,6 +26,9 @@ class TerminalWidget final : public QWidget {
int cell_width_ = 9;
int cell_height_ = 19;
bool grid_ready_ = false;
// Uncommitted input-method composition. Commits go to the session as UTF-8;
// cancelling is simply an empty preedit, so no partial text is ever written.
QString preedit_;
void refresh_metrics() {
const QFontMetricsF metrics(font());
cell_width_ = (std::max)(1, qCeil(metrics.horizontalAdvance(QLatin1Char('M'))));
Expand Down Expand Up @@ -120,6 +124,28 @@ class TerminalWidget final : public QWidget {
event->accept();
} else event->ignore();
}
// The candidate window needs the caret cell, not the widget origin.
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override {
if (query == Qt::ImCursorRectangle && grid_ready_) {
const auto& screen = model_.screen();
return QRect(margin + screen.column * cell_width_, margin + screen.row * cell_height_, cell_width_, cell_height_);
Comment on lines +129 to +131

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Notify the input method when the caret rectangle changes

During an active composition, shell output or a resize/font change can move the terminal caret, but refresh_session() only schedules painting and no code calls QGuiApplication::inputMethod()->update(...). Returning the latest rectangle from this query does not notify the platform that it changed: Qt requires input items to report query changes, and the pinned Qt 6.10.3 Windows implementation repositions the native candidate window through cursorRectangleChanged. Consequently, the preview can move while the candidate window remains at its previous position. Notify the input method with Qt::ImCursorRectangle when the focused terminal's caret geometry changes.

Useful? React with 👍 / 👎.

}
if (query == Qt::ImSurroundingText) return QString();
if (query == Qt::ImCurrentSelection) return QString();
if (query == Qt::ImCursorPosition) return 0;
return QWidget::inputMethodQuery(query);
}
void inputMethodEvent(QInputMethodEvent* event) override {
const QString preedit = event->preeditString();
if (preedit != preedit_) { preedit_ = preedit; update(); }
const QString commit = event->commitString();
if (!commit.isEmpty()) {
model_.send(commit.toUtf8());
preedit_.clear();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the new preedit when an event also commits text

When an IME commits part of a composition while continuing the remainder, a single event can contain both a nonempty commitString() and a nonempty preeditString(). This handler first stores the new preedit, then unconditionally clears it, making the ongoing composition disappear until another preedit event arrives. Qt's event contract treats the commit and replacement preedit as separate operations. Forward the committed text while retaining the event's new preedit; an empty preedit already handles composition completion or cancellation.

Useful? React with 👍 / 👎.

update();
}
event->accept();
}
void paintEvent(QPaintEvent*) override {
QPainter painter(this);
painter.fillRect(rect(), colour(-1, false));
Expand Down Expand Up @@ -149,6 +175,17 @@ class TerminalWidget final : public QWidget {
painter.fillRect(QRect(margin + screen.column * cell_width_, margin + (screen.row + 1) * cell_height_ - 2,
cell_width_, 2), colour(-1, true));
}
if (!preedit_.isEmpty()) {
// Compose in place at the caret; the committed text is what reaches
// the session, this preview is presentation only.
const QRectF area(margin + screen.column * cell_width_, margin + screen.row * cell_height_,
static_cast<qreal>(cell_width_) * static_cast<qreal>(preedit_.size()),
static_cast<qreal>(cell_height_));
auto compose_font = base_font; compose_font.setUnderline(true);
painter.setFont(compose_font);
painter.setPen(colour(7, true));
painter.drawText(area, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, preedit_);
}
painter.setFont(base_font);
painter.setPen(model_.error().isEmpty() ? colour(8, true) : colour(9, true));
const QString status = !model_.error().isEmpty() ? model_.error() : model_.finished() ?
Expand All @@ -171,7 +208,9 @@ class TerminalWidget final : public QWidget {
fixed.setStyleHint(QFont::TypeWriter); fixed.setPointSize(11); setFont(fixed);
refresh_metrics();
setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_InputMethodEnabled, false);
// #155: composition, commit and cancel arrive through Qt's input method
// events; the widget only forwards committed text and never guesses.
setAttribute(Qt::WA_InputMethodEnabled, true);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
resize(sizeHint());
grid_ready_ = true;
Expand Down