Repository navigation
feat(qt): Qt 终端接入 IME 组合/提交/取消 (#155) #205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| #include <QCloseEvent> | ||
| #include <QFontDatabase> | ||
| #include <QFontMetricsF> | ||
| #include <QInputMethodEvent> | ||
| #include <QKeyEvent> | ||
| #include <QResizeEvent> | ||
| #include <QShowEvent> | ||
|
|
@@ -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')))); | ||
|
|
@@ -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_); | ||
| } | ||
| 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(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an IME commits part of a composition while continuing the remainder, a single event can contain both a nonempty Useful? React with 👍 / 👎. |
||
| update(); | ||
| } | ||
| event->accept(); | ||
| } | ||
| void paintEvent(QPaintEvent*) override { | ||
| QPainter painter(this); | ||
| painter.fillRect(rect(), colour(-1, false)); | ||
|
|
@@ -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() ? | ||
|
|
@@ -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; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 callsQGuiApplication::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 throughcursorRectangleChanged. Consequently, the preview can move while the candidate window remains at its previous position. Notify the input method withQt::ImCursorRectanglewhen the focused terminal's caret geometry changes.Useful? React with 👍 / 👎.