QtのQGraphicsViewでIME入力を完璧に制御!`inputMethodEvent`徹底解説
IMEとは何か?
QGraphicsView::inputMethodEvent()
の役割
QGraphicsView
は、QGraphicsScene
の内容を表示するためのウィジェットです。このビュー上でテキスト入力を受け付ける場合(例えば、テキストアイテムを編集する際など)、IMEからのイベントを適切に処理する必要があります。
inputMethodEvent()
は、QGraphicsViewがIMEからのイベント(QInputMethodEvent
)を受け取ったときに呼び出される関数です。このイベントには、以下のような情報が含まれます。
- カーソルの位置、書式情報など
プリエディットテキスト内でのカーソル位置や、下線などの書式情報。 - プリエディットテキスト (preedit text)
まだ確定されていない、変換中のテキスト(例: ひらがなで入力された文字)。 - コミットされたテキスト (committed text)
確定された、表示すべきテキスト。
この関数をオーバーライドする理由
通常、QGraphicsView
は、デフォルトの実装でIMEからのイベントを処理し、シーン内の適切なテキストアイテムに転送します。しかし、以下のような場合には、この関数をオーバーライドしてカスタムの処理を行う必要があります。
- IMEによるビューポートのスクロール
IMEの入力領域がビューポートからはみ出す場合に、自動的にスクロールするなどの振る舞いを実装したい場合。 - カスタムテキスト入力ウィジェット
QGraphicsView
内に独自のカスタムテキスト入力ウィジェットを実装している場合。 - 特定のIME動作の制御
特定の種類のIME入力(例: 候補の表示方法、変換の確定タイミング)を細かく制御したい場合。
QInputMethodEvent
は、QWidget::inputMethodEvent()
と同様のイベント構造を持っています。- この関数をオーバーライドする際は、通常、基底クラスの
QGraphicsView::inputMethodEvent(event)
を呼び出すことを忘れないでください。これにより、デフォルトの処理が引き続き行われます。
よくあるエラーと問題
-
- 原因1:
Qt::WA_InputMethodEnabled
属性が設定されていない。QGraphicsView
がIMEイベントを受け取るためには、このウィジェット属性が有効になっている必要があります。 - 原因2: フォーカスがない。
QGraphicsView
またはそのシーン内のテキスト入力可能なアイテムにフォーカスがない場合、IMEイベントは発生しません。 - 原因3: イベントが途中で消費されている。
QGraphicsView
の親ウィジェットや、QGraphicsScene
、またはシーン内の他のアイテムが、inputMethodEvent
よりも上位のイベントハンドラ(例:eventFilter
やkeyPressEvent
など)でIMEイベントを処理してしまっている可能性があります。 - 原因4:
QGraphicsTextItem
を使用している場合、適切なsetTextInteractionFlags
が設定されていない。QGraphicsTextItem
をテキスト入力可能にするには、setTextInteractionFlags(Qt::TextEditorInteraction)
などを設定する必要があります。これにより、QGraphicsTextItem
がIMEイベントを自動的に処理するようになります。
- 原因1:
-
IME入力が正しく反映されない(変換候補が出ない、文字化けする)
- 原因1:
QGraphicsView::inputMethodEvent(event)
の呼び出し忘れ。 カスタムでinputMethodEvent
をオーバーライドした場合、基底クラスのメソッドを呼び出さないと、QtのデフォルトのIME処理ロジックが実行されず、変換候補の表示や確定などの処理が正しく行われません。 - 原因2:
QInputMethodQuery
の処理不足。 IMEは、 surrounding text(周辺テキスト)や cursor position(カーソル位置)などの情報をQInputMethodQuery
を通じて問い合わせてきます。QGraphicsView
やその中のテキストアイテムがこれらの情報を適切に提供しないと、IMEが正しく機能しないことがあります。特にカスタムのテキスト入力UIを実装している場合に問題になりやすいです。QGraphicsScene::inputMethodQuery()
やQGraphicsItem::inputMethodQuery()
をオーバーライドして、適切な情報を返す必要があります。 - 原因3: レンダリングの問題。 IMEによって表示されるプリエディットテキスト(変換中の文字)や確定されたテキストが、ビューポートの更新と同期されず、表示がずれたり、ちらついたり、全く表示されないことがあります。
- 原因1:
-
パフォーマンスの問題
- 原因: 頻繁なビューポートの更新。
inputMethodEvent
内でテキストの更新が行われるたびに、QGraphicsView
全体の再描画が発生すると、特に複雑なシーンの場合にパフォーマンスが低下することがあります。
- 原因: 頻繁なビューポートの更新。
-
inputMethodEvent
が呼び出されるか確認するinputMethodEvent
の最初にqDebug()
などでログを出力し、関数が呼び出されているか、そしてどのようなQInputMethodEvent
が渡されているかを確認します。qDebug() << "inputMethodEvent called with type:" << event->type();
qDebug() << "Committed text:" << event->committedText();
qDebug() << "Preedit text:" << event->preeditString();
QInputMethodEvent::Type
がQEvent::InputMethod
であるかを確認します。
-
フォーカスの確認
QGraphicsView::setFocusPolicy(Qt::StrongFocus)
を設定し、ウィジェットがキーボードフォーカスを受け取れるようにします。QGraphicsView::setFocus()
を呼び出して、明示的にフォーカスを設定してみます。- シーン内のテキストアイテムにフォーカスを当てる必要がある場合は、そのアイテムの
setFocus(Qt::OtherFocusReason)
などを呼び出します。
-
イベント伝播の確認
QGraphicsView
の親ウィジェットや、QGraphicsScene
、シーン内の他のアイテムのイベントハンドラ(特にeventFilter
やkeyPressEvent
、mousePressEvent
など)にデバッグ出力を追加し、IMEイベントがどこで処理されているか(あるいは消費されているか)を確認します。- カスタムイベントハンドラをオーバーライドしている場合、必要に応じて基底クラスのメソッド(例:
QGraphicsView::inputMethodEvent(event);
)を必ず呼び出すようにします。
-
Qt::WA_InputMethodEnabled
属性の設定QGraphicsView
のコンストラクタなどで、以下のコードを追加します。setAttribute(Qt::WA_InputMethodEnabled, true);
-
QGraphicsTextItem
の適切な設定- もし
QGraphicsTextItem
を使っているなら、テキスト入力に必要なフラグを設定します。QGraphicsTextItem *textItem = new QGraphicsTextItem("初期テキスト"); textItem->setTextInteractionFlags(Qt::TextEditorInteraction); // これが重要 textItem->setFlag(QGraphicsItem::ItemIsFocusable); // フォーカス可能にする scene->addItem(textItem); textItem->setFocus(); // 必要に応じてフォーカスを設定
- もし
-
描画更新の最適化
- IME入力時の表示の問題は、ビューポートの更新モードやキャッシュモードに関連している場合があります。
setViewportUpdateMode()
を試してみる。QGraphicsView::FullViewportUpdate
(常にビューポート全体を更新。遅いが確実)QGraphicsView::BoundingRectViewportUpdate
(更新が必要な最小限の矩形を更新。通常これが良い)
setCacheMode(QGraphicsView::CacheBackground)
などを試すこともできますが、IME入力のような動的なコンテンツには逆効果になることもあります。
通常、QGraphicsView
内でテキスト入力を扱う場合、直接QGraphicsView::inputMethodEvent()
をオーバーライドするよりも、QGraphicsTextItem
を利用するか、カスタムのQGraphicsItem
を作成してその中でIMEイベントを処理する方が一般的です。
しかし、QGraphicsView
レベルでIMEイベントを処理する必要があるシナリオ(例えば、カスタムのテキスト入力UIをビューポート全体で制御する場合など)も存在します。
ここでは、QGraphicsView
をサブクラス化し、inputMethodEvent()
をオーバーライドする基本的な例と、より複雑なIME処理に必要なinputMethodQuery()
も考慮に入れた例を説明します。
例1: QGraphicsView::inputMethodEvent()
の基本的なオーバーライド
この例では、MyGraphicsView
というカスタムビューを作成し、IMEからの入力があった際に、そのテキストを単にqDebug()
で出力します。実際には、このテキストをシーン内の適切なアイテムに適用するロジックが必要になります。
mygraphicsview.h
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H
#include <QGraphicsView>
#include <QInputMethodEvent>
#include <QDebug> // デバッグ出力用
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
protected:
// IMEイベントを処理するためにオーバーライド
void inputMethodEvent(QInputMethodEvent *event) override;
// IMEが追加情報を要求するためにオーバーライド
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;
// キーイベントもIMEと関連することがあるため、追加
void keyPressEvent(QKeyEvent *event) override;
private:
// 簡単なテキスト表示用(実際のアプリケーションではQGraphicsTextItemなどを使用)
QString currentText;
QPointF textPosition;
};
#endif // MYGRAPHICSVIEW_H
mygraphicsview.cpp
#include "mygraphicsview.h"
#include <QGraphicsScene>
#include <QKeyEvent> // QKeyEventを使用するため
#include <QPainter> // 描画のために必要(ここでは簡易的に使用)
MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent)
{
// IMEイベントを受け取るように設定
setAttribute(Qt::WA_InputMethodEnabled, true);
// フォーカスを受け取れるように設定
setFocusPolicy(Qt::StrongFocus);
// テキスト表示の初期位置
textPosition = QPointF(50, 50);
}
void MyGraphicsView::inputMethodEvent(QInputMethodEvent *event)
{
// IMEイベントのデバッグ出力
qDebug() << "inputMethodEvent received:";
qDebug() << " Committed text:" << event->committedText();
qDebug() << " Preedit text:" << event->preeditString();
// 確定されたテキストを追加
if (!event->committedText().isEmpty()) {
currentText += event->committedText();
}
// 変換中のテキストを処理
// ここでは単純に置き換えるが、実際のアプリケーションではカーソル位置などを考慮する必要がある
QString newText = currentText;
if (!event->preeditString().isEmpty()) {
// 通常、preedit textはカーソル位置に挿入される
// ここでは、簡単のために末尾に追加または置き換える
// 実際のアプリでは、QTextDocumentやQTextCursorを使ってより正確に制御します
newText += event->preeditString(); // 末尾に追加する例
}
// シーンの更新を要求
// 実際には、テキストを描画するアイテムを更新し、そのアイテムのupdate()を呼び出す
viewport()->update(); // ビューポートを再描画
// 基底クラスのイベントハンドラを呼び出す
// これにより、IMEの標準的な処理(例:Qt内部でのIME状態の管理)が継続されます。
QGraphicsView::inputMethodEvent(event);
}
QVariant MyGraphicsView::inputMethodQuery(Qt::InputMethodQuery query) const
{
// IMEが周辺テキストやカーソル位置などの情報を要求する際に呼び出される
switch (query) {
case Qt::ImHints:
// 特殊なヒントがない場合はデフォルトを返す
return QVariant(Qt::InputMethodHints());
case Qt::ImCursorRectangle:
// 現在のテキスト入力位置のカーソル矩形をビューポート座標で返す
// ここでは、簡単なテキスト位置に合わせたダミーのカーソル矩形を返す
// 実際のアプリケーションでは、フォーカスのあるアイテムのカーソル位置を計算して返す
return QVariant(QRectF(textPosition.x() + fontMetrics().horizontalAdvance(currentText),
textPosition.y(),
1, // カーソル幅
fontMetrics().height()));
case Qt::ImFont:
// 現在のテキスト入力に使用されているフォントを返す
return QVariant(font());
case Qt::ImCurrentSelection:
// 現在の選択範囲を返す(選択がない場合は空文字列)
return QVariant(QString());
case Qt::ImSurroundingText:
// 周辺テキストを返す
return QVariant(currentText);
case Qt::ImAnchorRectangle:
// アンカー(入力開始点)の矩形を返す
return QVariant(QRectF(textPosition.x(), textPosition.y(),
fontMetrics().horizontalAdvance(currentText),
fontMetrics().height()));
case Qt::ImCursorPosition:
// 現在のカーソル位置(文字数)を返す
return QVariant(currentText.length());
default:
break;
}
// 基底クラスのメソッドを呼び出す
return QGraphicsView::inputMethodQuery(query);
}
void MyGraphicsView::keyPressEvent(QKeyEvent *event)
{
// IME変換中にEnterキーが押された場合、IMEが確定処理を行うため、
// keyPressEventで直接テキストを追加するのではなく、inputMethodEventで処理するのが適切です。
// しかし、IMEが関与しない直接のキー入力(例: Backspace, Delete)もここで処理します。
if (event->key() == Qt::Key_Backspace) {
if (!currentText.isEmpty()) {
currentText.chop(1); // 最後の文字を削除
viewport()->update();
}
event->accept();
} else if (event->key() == Qt::Key_Delete) {
// カーソル位置を考慮した削除が必要
event->accept();
} else {
// その他のキーは基底クラスに任せる(IMEイベントに変換される可能性あり)
QGraphicsView::keyPressEvent(event);
}
}
// 簡単な描画関数(通常はQGraphicsItemのpaint()で描画)
// この例ではビューポートに直接描画している
void MyGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
{
QGraphicsView::drawBackground(painter, rect); // 背景描画
painter->setFont(font());
painter->drawText(textPosition, currentText); // 現在のテキストを描画
}
main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include "mygraphicsview.h"
#include <QVBoxLayout>
#include <QWidget>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
scene->setSceneRect(0, 0, 800, 600); // シーンのサイズを設定
MyGraphicsView *view = new MyGraphicsView(scene);
view->setWindowTitle("Custom QGraphicsView with IME Input");
view->resize(800, 600);
// テキスト入力を促すためのQGraphicsTextItem
QGraphicsTextItem *instructionText = new QGraphicsTextItem("このビューで日本語入力してみてください。");
instructionText->setPos(10, 10);
scene->addItem(instructionText);
view->show();
// フォーカスをMyGraphicsViewに設定
view->setFocus();
return a.exec();
}
解説
- MyGraphicsViewクラス
QGraphicsView
を継承します。 setAttribute(Qt::WA_InputMethodEnabled, true);
: これが最も重要です。この属性を設定することで、ウィジェットがIMEからの入力イベントを受け入れるようになります。setFocusPolicy(Qt::StrongFocus);
: キーボードフォーカスを受け取れるように設定します。IMEからの入力は、フォーカスのあるウィジェットに送られます。inputMethodEvent(QInputMethodEvent *event)
:- IMEからテキスト入力があったときに呼び出されます。
event->committedText()
: 確定されたテキスト(例:「きょう」を確定すると「今日」)event->preeditString()
: 変換中のテキスト(例:「きょ」や「きょう」を変換中に表示されるひらがななど)- この例では、
currentText
というメンバ変数に確定されたテキストを追加し、preeditString()
を仮に結合して表示を更新しています。 - 重要
QGraphicsView::inputMethodEvent(event);
を呼び出すことで、Qtの標準的なIME処理パイプラインが実行されます。これを忘れると、IMEが正しく動作しない場合があります。
inputMethodQuery(Qt::InputMethodQuery query) const
:- IMEがウィジェットに対して、テキストの周辺情報(カーソル位置、周辺テキスト、フォントなど)を問い合わせる際に呼び出されます。
- IMEが正確な変換候補を提示したり、カーソル位置に変換中のテキストを表示したりするために、これらの情報が必要です。
Qt::ImCursorRectangle
:カーソルの表示位置をビューポート座標で返す必要があります。Qt::ImSurroundingText
:IMEが変換候補を絞り込む際に、既存のテキストを参照するために使われます。- カスタムのテキスト入力メカニズムを実装する場合、これらのクエリに正確な情報を返すことが非常に重要です。
keyPressEvent(QKeyEvent *event)
:- IMEイベントはキーイベントよりも優先されることが多いため、通常はIME処理に影響を与えません。
- しかし、BackspaceやDeleteのように、IMEが直接関与しないキー入力を処理する必要がある場合は、ここで処理します。
drawBackground(QPainter *painter, const QRectF &rect)
:- この例では、簡略化のため
QGraphicsView
の背景に直接テキストを描画しています。 - 実際のアプリケーションでは、
QGraphicsTextItem
を使用するか、カスタムのQGraphicsItem
を作成し、そのpaint()
メソッド内でテキストを描画し、テキストの更新に応じてアイテムのupdate()
を呼び出すのが適切です。
- この例では、簡略化のため
実行方法
- 上記の3つのファイルを同じディレクトリに保存します。
.pro
ファイルを作成します(例:ime_test.pro
)。QT += widgets SOURCES = main.cpp mygraphicsview.cpp HEADERS = mygraphicsview.h
- Qt Creatorでプロジェクトを開き、ビルドして実行します。
多くの場合、QGraphicsView
でテキスト入力を扱う一番簡単な方法は、QGraphicsTextItem
を利用することです。QGraphicsTextItem
は、内部でIMEイベントやキーイベントを自動的に処理するように設計されています。
QGraphicsView::inputMethodEvent()
を直接オーバーライドする代わりに、以下のようにQGraphicsTextItem
を使うだけで、IME入力が機能します。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsTextItem> // QGraphicsTextItemを使用
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 600);
QGraphicsView view(&scene);
view.setWindowTitle("QGraphicsTextItem with IME Input");
view.resize(800, 600);
// QGraphicsTextItemを作成し、編集可能にする
QGraphicsTextItem *textItem = new QGraphicsTextItem("ここをクリックして日本語を入力してください。");
textItem->setPos(50, 50);
// これが重要!テキストアイテムを編集可能にし、IMEイベントを処理するように設定
textItem->setTextInteractionFlags(Qt::TextEditorInteraction);
textItem->setFlag(QGraphicsItem::ItemIsFocusable); // フォーカス可能にする
scene.addItem(textItem);
// テキストアイテムに初期フォーカスを設定
textItem->setFocus();
view.show();
return a.exec();
}
QGraphicsTextItemを利用する (最も一般的で推奨される方法)
これは、QGraphicsView
内でテキスト入力を扱う最も簡単で堅牢な方法です。QGraphicsTextItem
は、リッチテキスト編集機能、カーソル管理、選択、そしてIME入力の処理を内蔵しています。
- デメリット
QGraphicsTextItem
の持つデフォルトの挙動(例:テキストのレイアウト、描画スタイル)を大きく変更したい場合に、柔軟性に限界があることがあります。- 非常に特殊なIMEの挙動(例:IMEが提供するカスタムUIとの連携)を実装するには、やはり
QGraphicsTextItem
をサブクラス化してinputMethodEvent()
などをオーバーライドする必要があるかもしれません。
- メリット
- IMEイベント、キーイベント、マウスイベントのほとんどを自動的に処理します。
QTextDocument
と連携し、リッチテキスト(HTML)の表示と編集が可能です。- カーソル、選択範囲の描画、Undo/Redoなどの機能が組み込まれています。
- 最小限のコードでテキスト入力機能を実現できます。
使用例
#include <QGraphicsTextItem>
// ...
QGraphicsTextItem *textItem = new QGraphicsTextItem("テキストを入力してください");
textItem->setPos(50, 50);
textItem->setTextInteractionFlags(Qt::TextEditorInteraction); // これが重要
textItem->setFlag(QGraphicsItem::ItemIsFocusable); // フォーカス可能にする
scene->addItem(textItem);
textItem->setFocus(); // 必要に応じてフォーカスを設定
QGraphicsProxyWidgetを利用して既存のQtウィジェットを埋め込む
QGraphicsProxyWidget
を使用すると、QLineEdit
やQTextEdit
のような標準のQtウィジェットをQGraphicsScene
内に埋め込むことができます。これらのウィジェットは、すでにIME処理を完全にサポートしています。
- デメリット
QGraphicsScene
とQGraphicsItem
の描画パフォーマンスを犠牲にする可能性があります。ウィジェットは通常のQtウィジェット描画パスを使用するため、大量に配置するとオーバーヘッドが大きくなることがあります。QGraphicsItem
の描画(QPainter
を使用)とQWidget
の描画が混在するため、視覚的な一貫性を保つのが難しい場合があります。- ウィジェットのサイズや位置の管理が、グラフィックスアイテムとは少し異なる場合があります。
- メリット
- Qtの標準テキスト入力ウィジェットのすべての機能(IME、Undo/Redo、コピー&ペースト、コンテキストメニューなど)をそのまま利用できます。
- コード量が非常に少なく、迅速にテキスト入力UIを構築できます。
- ウィジェットのスタイルやテーマがそのまま適用されます。
使用例
#include <QLineEdit>
#include <QGraphicsProxyWidget>
// ...
QLineEdit *lineEdit = new QLineEdit();
lineEdit->setPlaceholderText("IME入力テスト");
QGraphicsProxyWidget *proxyWidget = scene->addWidget(lineEdit);
proxyWidget->setPos(100, 100);
proxyWidget->setFocus(); // フォーカスを設定
カスタムQGraphicsItem内でIMEイベントを処理する
QGraphicsView
レベルではなく、特定のQGraphicsItem
がテキスト入力の責任を持つべき場合、そのカスタムアイテム内でIMEイベントを処理することができます。これは、独自の描画ロジックを持つカスタムテキストフィールドを作成する場合に特に有用です。
このアプローチでは、カスタムアイテムが以下の点を考慮する必要があります。
-
デメリット
- 実装が最も複雑です。
QGraphicsTextItem
が提供する多くの機能を、自分でゼロから実装する必要があります。 - 特に多言語入力やリッチテキスト表示など、高度な機能が必要な場合、開発コストが非常に高くなります。
- 実装が最も複雑です。
-
メリット
- テキスト描画とIME入力の挙動を完全に制御できます。
QGraphicsScene
の描画メカニズムと完全に統合されます。- 非常に軽量なカスタムテキスト入力UIを作成できます。
-
クリップボードと選択
コピー、ペースト、カット、テキスト選択のロジックも手動で実装する必要があります。 -
テキストの描画とカーソル
QPainter
を使用してテキストとカーソルを適切に描画し、テキストの更新に応じてupdate()
を呼び出す必要があります。 -
フォーカスの管理
アイテムがフォーカスを持つようにItemIsFocusable
フラグを設定し、setFocus()
を呼び出す必要があります。 -
QGraphicsItem::keyPressEvent(QKeyEvent *event)のオーバーライド
IMEが処理しない直接のキー入力(Backspace、Deleteなど)を処理します。 -
QGraphicsItem::inputMethodQuery(Qt::InputMethodQuery query) constのオーバーライド
IMEが周辺テキスト、カーソル位置、フォントなどの情報を問い合わせてくる際に、適切な情報を返します。 -
QGraphicsItem::inputMethodEvent(QInputMethodEvent *event)のオーバーライド
IMEからの入力イベントを処理します。
使用例 (概念的なコード - 詳細は省略)
// mycustomtextitem.h
#ifndef MYCUSTOMTEXTITEM_H
#define MYCUSTOMTEXTITEM_H
#include <QGraphicsItem>
#include <QInputMethodEvent>
#include <QTextCursor>
#include <QTextDocument> // テキスト管理のため
class MyCustomTextItem : public QGraphicsItem
{
public:
MyCustomTextItem(QGraphicsItem *parent = nullptr);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
protected:
void inputMethodEvent(QInputMethodEvent *event) override;
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;
void keyPressEvent(QKeyEvent *event) override;
void focusInEvent(QFocusEvent *event) override;
void focusOutEvent(QFocusEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
private:
QTextDocument *document; // テキストコンテンツを管理
QTextCursor cursor; // カーソル位置を管理
// その他の描画プロパティ(色、フォントなど)
};
#endif // MYCUSTOMTEXTITEM_H
// mycustomtextitem.cpp (抜粋)
#include "mycustomtextitem.h"
#include <QPainter>
#include <QTextOption>
#include <QKeyEvent>
MyCustomTextItem::MyCustomTextItem(QGraphicsItem *parent)
: QGraphicsItem(parent),
document(new QTextDocument(this))
{
setFlag(QGraphicsItem::ItemIsFocusable);
setFlag(QGraphicsItem::ItemSendsGeometryChanges); // ジオメトリ変更通知を有効にする
setTextInteractionFlags(Qt::TextEditorInteraction); // これも設定可能
}
QRectF MyCustomTextItem::boundingRect() const
{
// テキストコンテンツの境界を返す
return document->documentLayout()->frameBoundingRect(document->rootFrame());
}
void MyCustomTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
// テキストの描画
painter->save();
document->drawContents(painter, QRectF(0, 0, boundingRect().width(), boundingRect().height()));
painter->restore();
// カーソルの描画(フォーカスがある場合)
if (hasFocus()) {
QTextLayout *layout = document->documentLayout()->frameAt(0)->layout();
QTextLine line = layout->lineForTextPosition(cursor.position());
QRectF cursorRect = line.cursorToRectangle(cursor.positionInBlock());
painter->fillRect(cursorRect, Qt::black); // 簡単なカーソル描画
}
}
void MyCustomTextItem::inputMethodEvent(QInputMethodEvent *event)
{
// IMEからの確定テキストを挿入
if (!event->committedText().isEmpty()) {
cursor.insertText(event->committedText());
}
// 変換中のテキストを処理 (QTextCursorで置き換える)
cursor.beginEditBlock(); // 編集ブロック開始
cursor.removeSelectedText(); // 現在のプリエディットがあれば削除
cursor.insertText(event->preeditString(), event->attributes()); // プリエディットテキストを挿入
cursor.endEditBlock(); // 編集ブロック終了
update(); // 描画更新
event->accept(); // イベントを処理済みとしてマーク
}
QVariant MyCustomTextItem::inputMethodQuery(Qt::InputMethodQuery query) const
{
// IMEが要求する情報を返す
switch (query) {
case Qt::ImCursorRectangle:
{
// カーソル位置の矩形をアイテム座標で返す
QTextLayout *layout = document->documentLayout()->frameAt(0)->layout();
QTextLine line = layout->lineForTextPosition(cursor.position());
QRectF rect = line.cursorToRectangle(cursor.positionInBlock());
return QVariant(rect);
}
case Qt::ImSurroundingText:
return QVariant(document->toPlainText());
case Qt::ImCursorPosition:
return QVariant(cursor.position());
case Qt::ImFont:
return QVariant(document->defaultFont());
// 他のクエリも必要に応じて実装
default:
break;
}
return QGraphicsItem::inputMethodQuery(query); // 基底クラスに任せる
}
void MyCustomTextItem::keyPressEvent(QKeyEvent *event)
{
// IMEが処理しないキー(例:Backspace)を処理
if (event->key() == Qt::Key_Backspace) {
cursor.deletePreviousChar();
update();
event->accept();
} else {
// その他のキーは、QTextDocumentのデフォルト処理に任せるか、カスタム処理
document->undoStack()->beginMacro("keyPress"); // undo/redo のため
cursor.insertText(event->text());
document->undoStack()->endMacro();
update();
event->accept();
}
}
void MyCustomTextItem::focusInEvent(QFocusEvent *event)
{
Q_UNUSED(event);
update(); // フォーカス表示のために再描画
}
void MyCustomTextItem::focusOutEvent(QFocusEvent *event)
{
Q_UNUSED(event);
update(); // フォーカス表示の解除のために再描画
}
void MyCustomTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
// マウスでカーソル位置を設定
QTextLine line = document->documentLayout()->frameAt(0)->layout()->lineForTextPosition(0); // 簡単な例
int pos = line.textPositionAt(event->pos().x()); // 簡易的な位置計算
cursor.setPosition(pos);
update();
QGraphicsItem::mousePressEvent(event);
}
- 独自の描画や非常に特殊な入力ロジックが必要で、開発コストを許容できるなら
カスタムQGraphicsItem
内でinputMethodEvent()
とinputMethodQuery()
をオーバーライドして、ゼロから実装します。これは最も柔軟ですが、最も複雑なパスです。 - Qtの標準ウィジェットの機能が必須で、パフォーマンスがそれほど厳しくないなら
QGraphicsProxyWidget
を検討しましょう。 - 簡単なテキスト入力で十分なら
QGraphicsTextItem
を使いましょう。これが最も手間がかからず、堅牢です。