QtのIME連携を徹底解説!QGraphicsView::inputMethodQuery()の基本と活用

2025-05-27

QVariant QGraphicsView::inputMethodQuery(Qt::InputMethodQuery query) const とは

QGraphicsView::inputMethodQuery() は、Qtのグラフィックスビューフレームワークにおける、入力メソッド(IMEなど)からの問い合わせに応答するための仮想関数です。

この関数は QVariant 型を返します。QVariant はQtが提供する汎用的なデータ型で、さまざまな種類のデータを格納できます。inputMethodQuery() が返す値は、query 引数で指定された問い合わせの種類に応じて変化します。

パラメータ

  • query (型: Qt::InputMethodQuery): 入力メソッドがどのような情報を要求しているかを示す列挙型です。例えば、以下のような問い合わせがあります。
    • Qt::InputMethodQuery::Position: カーソル位置のグローバル座標
    • Qt::InputMethodQuery::CursorRectangle: カーソル位置の矩形領域
    • Qt::InputMethodQuery::Font: 現在のテキストのフォント情報
    • Qt::InputMethodQuery::SelectedText: 選択されているテキスト
    • Qt::InputMethodQuery::SurroundingText: カーソル周辺のテキスト
    • Qt::InputMethodQuery::DoubleClickInterval: ダブルクリック間隔
    • など

戻り値

  • QVariant: query 引数で指定された情報に応じた QVariant オブジェクトを返します。例えば、Qt::InputMethodQuery::Position の場合は QPoint を格納した QVariantQt::InputMethodQuery::SelectedText の場合は QString を格納した QVariant が返されます。

なぜこれが重要なのか?

QGraphicsView は、QGraphicsScene 上のグラフィックアイテムを表示するためのウィジェットです。通常、テキスト入力は QLineEditQTextEdit のような専用のウィジェットで行われますが、QGraphicsScene 上のカスタムアイテムでテキスト入力を処理する場合(例えば、シーン上に配置されたテキストボックスのようなアイテム)、IMEからの問い合わせに適切に応答する必要があります。

QGraphicsView::inputMethodQuery() を再実装することで、入力メソッドに対してグラフィックスビュー内のテキスト入力状況を正確に伝え、IMEの機能(変換候補の表示、カーソル追従など)を正しく動作させることができます。

例えば、IMEが変換候補ウィンドウを表示する際に、カーソル位置を要求してきます。もし inputMethodQuery() がその情報を提供しないと、変換候補ウィンドウが意図しない場所に表示されたり、全く表示されなかったりする可能性があります。

もし QGraphicsView のサブクラスで独自のテキスト入力処理を行う場合、inputMethodQuery() をオーバーライドして、現在のカーソル位置やテキスト内容などを入力メソッドに提供する必要があります。

#include <QGraphicsView>
#include <QVariant>
#include <QGraphicsTextItem> // 例としてテキストアイテムを使用する場合

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT

public:
    explicit MyGraphicsView(QGraphicsScene* scene, QWidget* parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        // ...
    }

protected:
    QVariant inputMethodQuery(Qt::InputMethodQuery query) const override
    {
        // シーン内のアクティブなテキスト入力アイテムを取得する例
        QGraphicsTextItem* activeTextItem = findActiveTextItem(); // 仮の関数

        if (activeTextItem) {
            switch (query) {
                case Qt::InputMethodQuery::Position:
                {
                    // カーソル位置をビュー座標系で返す
                    // activeTextItemからカーソル位置のシーン座標を取得し、ビュー座標に変換する
                    QPointF sceneCursorPos = activeTextItem->textCursor().positionInDocument(); // 例
                    return mapFromScene(sceneCursorPos);
                }
                case Qt::InputMethodQuery::CursorRectangle:
                {
                    // カーソル位置の矩形領域をビュー座標系で返す
                    // activeTextItemからカーソル矩形のシーン座標を取得し、ビュー座標に変換する
                    QRectF sceneCursorRect = activeTextItem->textCursor().blockCharRect(); // 例
                    return mapFromScene(sceneCursorRect).boundingRect();
                }
                case Qt::InputMethodQuery::SurroundingText:
                {
                    // カーソル周辺のテキストを返す
                    return QVariant(activeTextItem->toPlainText());
                }
                case Qt::InputMethodQuery::SelectedText:
                {
                    // 選択されているテキストを返す
                    return QVariant(activeTextItem->textCursor().selectedText());
                }
                // 他のクエリに応答する
                default:
                    break;
            }
        }
        // デフォルトの動作に任せる
        return QGraphicsView::inputMethodQuery(query);
    }

private:
    QGraphicsTextItem* findActiveTextItem() const
    {
        // 現在フォーカスを持っている、またはテキスト入力中のQGraphicsTextItemを見つけるロジック
        // これはアプリケーションによって大きく異なる
        // 例えば、QGraphicsScene::focusItem() を確認するなど
        return nullptr; // 仮の実装
    }
};
  • Qt 5.15以降では、QGraphicsView::IndirectPainting フラグが最適化フラグとして設定されていない限り、この関数は呼び出されません。これは、IME連携のパフォーマンス最適化のためです。
  • 通常、QGraphicsTextItem はすでに inputMethodQuery() を適切に処理しているため、QGraphicsView のサブクラスでこれを直接オーバーライドする必要があるのは、独自のテキスト入力メカニズムを持つカスタムアイテムを扱う場合などに限られます。
  • 上記のコードは概念を示すためのもので、実際のアプリケーションでは findActiveTextItem() の実装や、QGraphicsTextItem 以外のカスタムアイテムでのテキスト入力処理など、より複雑なロジックが必要になります。


これは最も一般的な問題です。IMEは、カーソル位置、周囲のテキスト、選択範囲などの情報を inputMethodQuery() を通じて問い合わせてきます。これらの情報が適切に返されないと、IMEは変換候補を表示したり、カーソルに追従したりすることができません。

考えられる原因

  • IndirectPainting フラグ
    Qt 5.15以降では、QGraphicsView::IndirectPainting フラグが設定されていない場合、inputMethodQuery() が呼び出されないことがあります。IME連携のパフォーマンス最適化のためです。明示的にこのフラグを設定する必要があるかもしれません。
  • Qt::InputMethodQuery::InputMethodHints の設定不足
    Qt::InputMethodHints を使用して、入力フィールドのタイプ(例: Qt::InputMethodHint::ImhDialableCharactersOnly など)をIMEに伝えることで、より適切な変換候補が表示されることがあります。これは通常、inputMethodQuery(Qt::InputMethodQuery::InputMethodHints) で返されます。
  • QtバージョンとIMEの互換性
    稀に、特定のQtバージョンやOSのIMEとの間に互換性の問題がある場合があります。最新のQtバージョンに更新したり、OSのIME設定を確認したりすると解決することがあります。
  • テキスト情報の不足
    Qt::InputMethodQuery::SurroundingTextQt::InputMethodQuery::SelectedText に対して、現在のテキストコンテンツや選択範囲が正しく返されていない場合、IMEの予測変換などが機能しません。
  • フォーカスの問題
    QGraphicsView またはその中の QGraphicsItem が入力フォーカス(Qt::WidgetFocus または Qt::StrongFocus)を持っていない場合、IMEは問い合わせを行いません。setFocus()setFlag(QGraphicsItem::ItemIsFocusable, true) などでフォーカス可能にし、実際にフォーカスが当たっていることを確認してください。
  • 不正確な座標変換
    PositionCursorRectangle を返す際に、ビュー座標系(QGraphicsView のクライアント領域)での正しい座標に変換されていない可能性があります。QGraphicsItem のローカル座標、シーン座標、ビュー座標の間で正しく変換を行う必要があります。mapToScene(), mapFromScene(), mapToParent(), mapFromParent() などの関数を適切に使用してください。
  • inputMethodQuery() の未実装または不完全な実装
    QGraphicsView のサブクラスで inputMethodQuery() をオーバーライドしていないか、必要な Qt::InputMethodQuery タイプ(例: Qt::InputMethodQuery::Position, Qt::InputMethodQuery::CursorRectangle, Qt::InputMethodQuery::SurroundingText)に対して適切な値を返していない可能性があります。

トラブルシューティング

  1. デバッグ出力の追加
    inputMethodQuery() 関数内に qDebug() を追加し、どの query が要求されているか、そしてどのような値が返されているかを確認します。
    QVariant MyGraphicsView::inputMethodQuery(Qt::InputMethodQuery query) const {
        qDebug() << "Input method query:" << query;
        QVariant result = QGraphicsView::inputMethodQuery(query); // デフォルトの動作
        // 必要に応じてカスタムロジック
        switch (query) {
            case Qt::InputMethodQuery::Position:
                qDebug() << "  Position:" << result.toPoint();
                break;
            case Qt::InputMethodQuery::CursorRectangle:
                qDebug() << "  CursorRectangle:" << result.toRect();
                break;
            case Qt::InputMethodQuery::SurroundingText:
                qDebug() << "  SurroundingText:" << result.toString();
                break;
            // ...
        }
        return result;
    }
    
  2. 座標系の確認
    返される座標が QGraphicsView のクライアント領域における正しい位置になっているか、特に mapToScene()mapFromScene() の使用方法が正しいか確認します。
  3. フォーカス状態の確認
    QGraphicsView や対象の QGraphicsItemQFocusEvent を受け取っているか、hasKeyboardInput()scene()->focusItem() などでフォーカス状態を確認します。
  4. 公式ドキュメントの参照
    Qt::InputMethodQuery の各列挙値が具体的にどのような情報を期待しているか、公式ドキュメントを再確認します。

QVariant 関連のエラー

QVariant は型安全なコンテナですが、誤った型を格納しようとしたり、期待する型で取り出せなかったりするとエラーや未定義の動作につながります。

考えられる原因

  • QVariant の初期化忘れ
    何も格納されていない QVariant を返すと、IMEが期待する情報が得られず、問題が発生します。
  • 型変換の失敗
    QVariant::toPoint(), QVariant::toString(), QVariant::toRect() などのメソッドで、格納されているデータと異なる型に変換しようとすると、無効な値が返されることがあります。
  • 誤った型の格納
    例えば、Qt::InputMethodQuery::Position には QPoint を格納すべきですが、QSizeF を格納してしまっているなど。

トラブルシューティング

  1. QVariant::type() または QVariant::canConvert() の確認
    デバッグ中に QVariant がどの型を格納しているか、または特定の型に変換可能かを確認できます。
    QVariant result = /* ... */;
    qDebug() << "QVariant type:" << result.type();
    if (result.canConvert<QPoint>()) {
        qDebug() << "Can convert to QPoint.";
    }
    
  2. 適切な型での QVariant の構築
    QVariant を構築する際に、期待されるC++の型で明示的に構築するようにしてください。
    return QVariant(QPoint(x, y)); // QPointを格納
    return QVariant(QString("some text")); // QStringを格納
    
  3. デフォルト値の返却
    不明な query タイプや、情報を返せない状況では、QGraphicsView::inputMethodQuery(query) を呼び出してデフォルトの QVariant を返すようにします。これにより、予期せぬクラッシュや未定義の動作を防げます。

iOS/macOS 特有の問題(特定のQtフォーラムの投稿から)

稀に、特定のプラットフォーム(特にiOS)で inputMethodQuery に関連するエラーメッセージが表示されることがあります。


No such method GraphicsView::inputMethodQuery(Qt::InputMethodQuery,QVariant)

考えられる原因

  • Qtのバグ
    ごく稀に、特定のQtバージョンやプラットフォームにおけるバグが原因である可能性もゼロではありません。
  • 仮想キーボードモジュールの問題
    Qtの仮想キーボードモジュールを使用している場合、そのモジュールがプラットフォーム固有のIMEと連携する際に問題を起こしている可能性があります。
  • シグネチャの不一致
    Qtのバージョンやビルド環境によっては、inputMethodQuery の期待されるシグネチャ(引数の数や型)が異なる場合があります。しかし、公式の QGraphicsView::inputMethodQuery は引数が1つ (Qt::InputMethodQuery) のみであるため、このようなエラーが出た場合は、何らかの仮想キーボードモジュールやサードパーティのIME関連ライブラリが、Qtの内部APIを誤って呼び出そうとしている可能性があります。

トラブルシューティング

  1. 正確なエラーメッセージの確認
    コンパイラやランタイムが出力する正確なエラーメッセージを確認し、それが QVariant QGraphicsView::inputMethodQuery() に関連するものか確認します。
  2. Qtバージョンの確認
    使用しているQtの正確なバージョンを確認し、そのバージョンの公式ドキュメントで inputMethodQuery のシグネチャを確認します。
  3. 仮想キーボードの無効化/確認
    もし仮想キーボードモジュールを使用している場合は、それを一時的に無効にして問題が解決するか確認します。
  4. 関連するQtバグレポートの検索
    Qtのバグトラッカー(bugreports.qt.io)で、関連するエラーメッセージやプラットフォーム固有の問題について検索してみます。
  5. 最小再現コードの作成
    問題が複雑な場合は、問題が発生する最小限のコードを作成し、他の環境で再現するか確認します。

QVariant QGraphicsView::inputMethodQuery() は、QGraphicsView 上で高度なテキスト入力機能(特に多言語入力)を実装する上で非常に重要な関数です。エラーや問題が発生した場合は、主に以下の点を確認してください。

  • Qtのバージョンやプラットフォーム固有の問題でないか。
  • QVariant に正しい型のデータが格納されているか、そして適切に変換されているか。
  • QGraphicsView や対象の QGraphicsItem が入力フォーカスを持っているか。
  • 座標変換が正しく行われているか(シーン座標からビュー座標へ)。
  • inputMethodQuery() が適切にオーバーライドされ、必要な Qt::InputMethodQuery タイプに対して正しい情報(特に座標とテキスト)を返しているか。


ここでは、QGraphicsView のサブクラスで inputMethodQuery() をオーバーライドし、カスタムのテキスト入力を行うための簡単な例をいくつか示します。

QVariant QGraphicsView::inputMethodQuery() 関連のプログラミング例

例1: 最も基本的な inputMethodQuery() の実装

この例では、MyGraphicsView という QGraphicsView のサブクラスを作成し、inputMethodQuery() をオーバーライドして、最も基本的な情報(カーソル位置、周囲のテキスト)をIMEに提供する方法を示します。ここでは、シンプルにするために、QGraphicsScene 上の特定の QGraphicsTextItem を「アクティブなテキスト入力要素」として扱います。

mygraphicsview.h

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QGraphicsTextItem>
#include <QInputMethodEvent>
#include <QKeyEvent>
#include <QDebug> // デバッグ出力用

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT

public:
    explicit MyGraphicsView(QGraphicsScene* scene, QWidget* parent = nullptr);

protected:
    // IMEからの問い合わせに応答する
    QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;

    // キーイベントを処理してテキスト入力を模倣する
    void keyPressEvent(QKeyEvent *event) override;

    // IMEイベントを処理する
    void inputMethodEvent(QInputMethodEvent *event) override;

private:
    QGraphicsTextItem* activeTextItem; // 現在のテキスト入力要素

    // 実際のアプリケーションでは、フォーカスを持つアイテムを動的に管理します
    // この例ではシンプルにするため、コンストラクタで設定します
    void setActiveTextItem(QGraphicsTextItem* item);
};

#endif // MYGRAPHICSVIEW_H

mygraphicsview.cpp

#include "mygraphicsview.h"
#include <QGraphicsScene>
#include <QTextCursor>

MyGraphicsView::MyGraphicsView(QGraphicsScene* scene, QWidget* parent)
    : QGraphicsView(scene, parent), activeTextItem(nullptr)
{
    // IMEの動作に必要なInputMethodQuery::Positionの問い合わせを有効にする
    // Qt 5.15以降では、IME連携のパフォーマンス最適化のために、
    // InputMethodQuery::Position を必要とする場合に明示的に有効にする必要があります。
    // setInputMethodHints(Qt::InputMethodHint::ImhMultiLine); // 例: 複数行入力
    // setInputMethodHints(Qt::InputMethodHint::ImhHiddenText); // 例: パスワード入力など

    // デフォルトのビューポート更新モードではIMEが正しく動作しない場合があるため、
    // 必要に応じて更新モードを調整することも検討してください
    // setViewportUpdateMode(QGraphicsView::FullViewportUpdate); // 全体更新
}

void MyGraphicsView::setActiveTextItem(QGraphicsTextItem* item)
{
    if (activeTextItem) {
        activeTextItem->setTextInteractionFlags(Qt::NoTextInteraction);
    }
    activeTextItem = item;
    if (activeTextItem) {
        // テキスト入力のためにQGraphicsTextItemを編集可能にする
        activeTextItem->setTextInteractionFlags(Qt::TextEditorInteraction);
        activeTextItem->setFocus(); // フォーカスを設定
    }
}

QVariant MyGraphicsView::inputMethodQuery(Qt::InputMethodQuery query) const
{
    if (!activeTextItem) {
        return QGraphicsView::inputMethodQuery(query);
    }

    // デバッグ出力
    qDebug() << "inputMethodQuery called for:" << query;

    switch (query) {
        case Qt::InputMethodQuery::Position:
        {
            // カーソル位置をビューポート座標で返す
            // activeTextItemのカーソル位置(テキストドキュメント座標)を取得
            QTextCursor cursor = activeTextItem->textCursor();
            QRectF cursorPosRect = activeTextItem->mapRectToScene(cursor.blockCharRect()); // シーン座標
            QPoint globalCursorPos = mapToGlobal(mapFromScene(cursorPosRect.topLeft())); // グローバル座標

            qDebug() << "  Query Position: Global" << globalCursorPos;
            return QVariant(globalCursorPos);
        }
        case Qt::InputMethodQuery::CursorRectangle:
        {
            // カーソル位置の矩形領域をビューポート座標で返す
            QTextCursor cursor = activeTextItem->textCursor();
            QRectF sceneCursorRect = activeTextItem->mapRectToScene(cursor.blockCharRect()); // シーン座標
            QRect viewCursorRect = mapFromScene(sceneCursorRect).boundingRect(); // ビュー座標

            qDebug() << "  Query CursorRectangle: View" << viewCursorRect;
            return QVariant(viewCursorRect);
        }
        case Qt::InputMethodQuery::Font:
        {
            // 現在のフォントを返す
            QFont font = activeTextItem->font();
            qDebug() << "  Query Font:" << font.family();
            return QVariant(font);
        }
        case Qt::InputMethodQuery::SelectedText:
        {
            // 選択されているテキストを返す
            QString selectedText = activeTextItem->textCursor().selectedText();
            qDebug() << "  Query SelectedText:" << selectedText;
            return QVariant(selectedText);
        }
        case Qt::InputMethodQuery::SurroundingText:
        {
            // カーソル周辺のテキストを返す
            QString text = activeTextItem->toPlainText();
            qDebug() << "  Query SurroundingText:" << text;
            return QVariant(text);
        }
        case Qt::InputMethodQuery::CurrentSelectionStart:
        {
            // 選択範囲の開始位置を返す (テキストドキュメント内のオフセット)
            int start = activeTextItem->textCursor().selectionStart();
            qDebug() << "  Query CurrentSelectionStart:" << start;
            return QVariant(start);
        }
        case Qt::InputMethodQuery::CurrentSelectionLength:
        {
            // 選択範囲の長さを返す (テキストドキュメント内の文字数)
            int length = activeTextItem->textCursor().selectionEnd() - activeTextItem->textCursor().selectionStart();
            qDebug() << "  Query CurrentSelectionLength:" << length;
            return QVariant(length);
        }
        case Qt::InputMethodQuery::AnchorPosition:
        {
            // 選択範囲のアンカー位置(開始点)をグローバル座標で返す
            QTextCursor cursor = activeTextItem->textCursor();
            // 一般的には、選択範囲の開始点とカーソル位置をIMEに伝える
            // これはQTextCursor::position()やQTextCursor::anchor()の値を基に計算
            // ここでは簡易的にカーソル位置を返す
            QRectF cursorPosRect = activeTextItem->mapRectToScene(cursor.blockCharRect()); // シーン座標
            QPoint globalAnchorPos = mapToGlobal(mapFromScene(cursorPosRect.topLeft()));
            qDebug() << "  Query AnchorPosition:" << globalAnchorPos;
            return QVariant(globalAnchorPos);
        }
        case Qt::InputMethodQuery::InputMethodHints:
        {
            // 入力メソッドのヒントを返す(例: ImhMultiLine, ImhNoAutoUppercaseなど)
            // activeTextItem->inputMethodHints() を使うのが一般的
            Qt::InputMethodHints hints = Qt::InputMethodHint::ImhMultiLine; // 例として
            qDebug() << "  Query InputMethodHints:" << hints;
            return QVariant(hints);
        }
        default:
            break;
    }

    // その他の問い合わせは基底クラスに任せる
    return QGraphicsView::inputMethodQuery(query);
}

void MyGraphicsView::keyPressEvent(QKeyEvent *event)
{
    if (activeTextItem && activeTextItem->textInteractionFlags().testFlag(Qt::TextEditorInteraction)) {
        // IMEが有効な場合、keyPressEventの前にinputMethodEventが呼び出される
        // inputMethodEventが処理しないキーイベント(例: Enterキー、矢印キー)をここで処理
        if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
            // エンターキーが押されたら改行を追加(QGraphicsTextItemが自動で処理)
            // またはカスタムの動作
            qDebug() << "Enter key pressed";
        }
        // QGraphicsTextItemにキーイベントを転送
        QGraphicsView::keyPressEvent(event);
    } else {
        // アクティブなテキストアイテムがない場合、または編集モードでない場合
        QGraphicsView::keyPressEvent(event);
    }
}

void MyGraphicsView::inputMethodEvent(QInputMethodEvent *event)
{
    if (activeTextItem && activeTextItem->textInteractionFlags().testFlag(Qt::TextEditorInteraction)) {
        // IMEからのテキスト更新をQGraphicsTextItemに適用
        // 確定前テキスト (preedit) や確定済みテキスト (commit) を処理
        activeTextItem->inputMethodEvent(event);

        // テキストが変更されたことをビューに通知し、再描画を促す
        scene()->update(activeTextItem->boundingRect());

        // IMEイベントが処理されたことをQtに伝える
        event->accept();
    } else {
        QGraphicsView::inputMethodEvent(event); // デフォルトの処理
    }
}

main.cpp

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include "mygraphicsview.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600);

    MyGraphicsView view(&scene);
    view.setWindowTitle("Custom Graphics View with IME Support");
    view.resize(800, 600);
    view.setRenderHint(QPainter::Antialiasing);

    // テキスト入力用のQGraphicsTextItemを作成し、ビューに設定
    QGraphicsTextItem* textItem = new QGraphicsTextItem("Click here to type...");
    textItem->setPos(50, 50);
    textItem->setTextWidth(200);
    textItem->setFlag(QGraphicsItem::ItemIsSelectable, true);
    textItem->setFlag(QGraphicsItem::ItemIsFocusable, true); // フォーカス可能にする
    scene.addItem(textItem);

    // MyGraphicsViewにアクティブなテキストアイテムを設定
    // (通常はクリックイベントなどで動的に設定される)
    view.findChild<MyGraphicsView*>()->setActiveTextItem(textItem); // アクセスを簡単にするため

    view.show();

    return a.exec();
}

説明

  1. MyGraphicsView の作成
    QGraphicsView を継承した MyGraphicsView クラスを作成します。
  2. inputMethodQuery() のオーバーライド
    • IMEが要求する様々な Qt::InputMethodQuery の種類に応じて、適切な情報を QVariant で返します。
    • Qt::InputMethodQuery::PositionQt::InputMethodQuery::CursorRectangle は、IMEが変換候補ウィンドウをどこに表示すべきかを決定するために重要です。これらはビューポート座標(QGraphicsView のクライアント領域)で返す必要があります。mapFromScene() を使用して、シーン座標からビュー座標に変換しています。
    • Qt::InputMethodQuery::SurroundingTextQt::InputMethodQuery::SelectedText は、IMEが文脈に応じた変換候補を提供するために重要です。
    • Qt::InputMethodQuery::Font は、IMEがフォントサイズやスタイルを考慮して表示を調整するために使用されます。
  3. keyPressEvent() と inputMethodEvent()
    • inputMethodEvent() は、IMEがテキストの入力や確定(確定前の文字列 preedit や確定済みの文字列 commit)を行う際に呼び出されます。このイベントは QGraphicsTextItem に転送し、テキストの更新を処理させます。
    • keyPressEvent() は、IMEが処理しないキー(例: エンターキー、矢印キー)が押されたときに呼び出されます。ここでは、QGraphicsTextItem のデフォルトのキー処理に任せていますが、必要に応じてカスタムの動作を追加できます。
  4. QGraphicsTextItem の利用
    QGraphicsTextItem は、QGraphicsScene 上でテキストを扱うための便利なアイテムです。元々IME連携をサポートしているため、このアイテムにフォーカスを当てて TextEditorInteraction フラグを設定することで、比較的容易にテキスト入力機能を実現できます。
    • textItem->setFlag(QGraphicsItem::ItemIsFocusable, true); でフォーカス可能にし、textItem->setTextInteractionFlags(Qt::TextEditorInteraction); で編集モードにします。
  5. IME ヒント (setInputMethodHints)QGraphicsViewQGraphicsIteminputMethodHints() を設定することで、IMEにその入力フィールドの特性(例: 数字のみ、メールアドレスなど)を伝えることができます。これによりIMEはより適切な入力モードや候補を提供できます。

例2: カスタムテキスト入力アイテムでの inputMethodQuery() の利用 (概念)

もし、QGraphicsTextItem ではなく、完全に独自のカスタム描画を行うテキスト入力アイテム(例: MyCustomTextItem)を作成する場合、そのアイテム内でIMEからの問い合わせに応答する必要があります。

mycustomtextitem.h (簡易版)

#ifndef MYCUSTOMTEXTITEM_H
#define MYCUSTOMTEXTITEM_H

#include <QGraphicsItem>
#include <QString>
#include <QTextCursor> // 内部でテキストカーソルを管理する場合
#include <QFont>
#include <QInputMethodEvent>
#include <QKeyEvent>

class MyCustomTextItem : public QGraphicsItem
{
public:
    MyCustomTextItem(QGraphicsItem* parent = nullptr);

    QRectF boundingRect() const override;
    void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;

    // テキストとカーソル位置の管理(この例では簡略化)
    void setText(const QString& text);
    QString text() const;
    void setCursorPosition(int pos);
    int cursorPosition() const;

protected:
    // イベントハンドラ
    void focusInEvent(QFocusEvent *event) override;
    void focusOutEvent(QFocusEvent *event) override;
    void keyPressEvent(QKeyEvent *event) override;
    void inputMethodEvent(QInputMethodEvent *event) override;

    // inputMethodQuery をオーバーライドしてIMEに情報を提供する
    QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;

private:
    QString m_text;
    int m_cursorPosition;
    QFont m_font;
    QString m_preeditText; // 確定前テキスト(IMEからの入力)

    // カーソルの表示を制御するための情報
    bool m_cursorVisible;
    QTimer* m_cursorBlinkTimer; // カーソル点滅用
};

#endif // MYCUSTOMTEXTITEM_H

mycustomtextitem.cpp (抜粋)

#include "mycustomtextitem.h"
#include <QPainter>
#include <QTextOption>
#include <QTimer>
#include <QApplication> // QApplication::inputMethod() のために必要

MyCustomTextItem::MyCustomTextItem(QGraphicsItem* parent)
    : QGraphicsItem(parent),
      m_cursorPosition(0),
      m_font("Arial", 12),
      m_cursorVisible(true)
{
    setFlag(ItemIsFocusable); // フォーカス可能にする
    setFlag(ItemIsSelectable); // 選択可能にする(必要であれば)

    m_cursorBlinkTimer = new QTimer(this);
    connect(m_cursorBlinkTimer, &QTimer::timeout, this, [this](){
        m_cursorVisible = !m_cursorVisible;
        update(); // 再描画を要求
    });
    m_cursorBlinkTimer->start(QApplication::cursorBlinkTime());
}

QRectF MyCustomTextItem::boundingRect() const
{
    // アイテムの境界矩形を正確に返す必要があります
    // テキストのサイズとカーソル位置を考慮
    QFontMetrics fm(m_font);
    QRectF textRect = fm.boundingRect(m_text + m_preeditText);
    // カーソルや選択範囲も含むように調整
    return textRect.adjusted(-5, -5, 5, 5); // 適当な余白
}

void MyCustomTextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    painter->setFont(m_font);
    painter->setPen(Qt::black);

    // 確定済みテキストの描画
    painter->drawText(0, 0, m_text);

    // 確定前テキスト(下線など)の描画
    if (!m_preeditText.isEmpty()) {
        painter->setPen(Qt::blue); // 確定前テキストの色
        QFontMetrics fm(m_font);
        int committedTextWidth = fm.width(m_text);
        painter->drawText(committedTextWidth, 0, m_preeditText);

        // 下線を描画(IMEの確定前テキストの表現)
        QPointF startLine(committedTextWidth, fm.height() + 1);
        QPointF endLine(committedTextWidth + fm.width(m_preeditText), fm.height() + 1);
        painter->drawLine(startLine, endLine);
    }

    // カーソルの描画
    if (hasFocus() && m_cursorVisible) {
        QFontMetrics fm(m_font);
        int x = fm.width(m_text.left(m_cursorPosition) + m_preeditText); // 確定前テキストも考慮
        painter->drawLine(x, 0, x, fm.height()); // 簡単な縦線カーソル
    }
}

// テキストとカーソル位置の管理の実装
void MyCustomTextItem::setText(const QString& text) {
    m_text = text;
    update();
}
QString MyCustomTextItem::text() const {
    return m_text;
}
void MyCustomTextItem::setCursorPosition(int pos) {
    m_cursorPosition = qBound(0, pos, m_text.length());
    update();
}
int MyCustomTextItem::cursorPosition() const {
    return m_cursorPosition;
}


void MyCustomTextItem::focusInEvent(QFocusEvent *event)
{
    QGraphicsItem::focusInEvent(event);
    m_cursorBlinkTimer->start(); // フォーカスを得たらカーソル点滅開始
    update();
}

void MyCustomTextItem::focusOutEvent(QFocusEvent *event)
{
    QGraphicsItem::focusOutEvent(event);
    m_cursorBlinkTimer->stop(); // フォーカスを失ったらカーソル点滅停止
    m_cursorVisible = false;
    update();
}

void MyCustomTextItem::keyPressEvent(QKeyEvent *event)
{
    // IMEが処理しないキーイベント(例: Backspace, Delete, 矢印キー)をここで処理
    if (event->key() == Qt::Key_Backspace) {
        if (m_cursorPosition > 0) {
            m_text.remove(m_cursorPosition - 1, 1);
            m_cursorPosition--;
            update();
        }
    } else if (event->key() == Qt::Key_Left) {
        setCursorPosition(m_cursorPosition - 1);
    } else if (event->key() == Qt::Key_Right) {
        setCursorPosition(m_cursorPosition + 1);
    } else {
        // デフォルトのキーイベント処理
        QGraphicsItem::keyPressEvent(event);
    }
}

void MyCustomTextItem::inputMethodEvent(QInputMethodEvent *event)
{
    if (event->commitString().isEmpty()) {
        // 確定前テキストの更新
        m_preeditText = event->preeditString();
    } else {
        // 確定済みテキストの追加
        m_text.insert(m_cursorPosition, event->commitString());
        m_cursorPosition += event->commitString().length();
        m_preeditText.clear(); // 確定されたら確定前テキストはクリア
    }
    update(); // 変更を反映するために再描画
    event->accept();
}

QVariant MyCustomTextItem::inputMethodQuery(Qt::InputMethodQuery query) const
{
    qDebug() << "MyCustomTextItem inputMethodQuery called for:" << query;
    QFontMetrics fm(m_font);

    switch (query) {
        case Qt::InputMethodQuery::Position:
        {
            // カーソル位置をビューポート座標で返す
            // このアイテムのローカル座標でのカーソル位置を計算
            QPointF localCursorPos(fm.width(m_text.left(m_cursorPosition) + m_preeditText), 0);
            // シーン座標に変換し、さらにグローバル座標に変換
            // QGraphicsView::mapToGlobal は QWidget::mapToGlobal と異なり、ビューポートの親の座標系に依存する
            // 正しいグローバル座標を得るには、ビューポートウィジェットを使う
            if (scene() && scene()->views().size() > 0) {
                QGraphicsView* view = scene()->views().first(); // 最初のビューを取得
                QPointF sceneCursorPos = mapToScene(localCursorPos);
                QPoint viewCursorPos = view->mapFromScene(sceneCursorPos);
                QPoint globalPos = view->viewport()->mapToGlobal(viewCursorPos); // ビューポートのグローバル座標
                qDebug() << "  Query Position: Global" << globalPos;
                return QVariant(globalPos);
            }
            return QVariant(QPoint()); // ビューがない場合は空のQPoint
        }
        case Qt::InputMethodQuery::CursorRectangle:
        {
            // カーソル位置の矩形領域をビューポート座標で返す
            QPointF localCursorPos(fm.width(m_text.left(m_cursorPosition) + m_preeditText), 0);
            QRectF localCursorRect(localCursorPos, QSizeF(1, fm.height())); // 幅1ピクセルの縦線カーソル
            if (scene() && scene()->views().size() > 0) {
                QGraphicsView* view = scene()->views().first();
                QRectF sceneCursorRect = mapRectToScene(localCursorRect);
                QRect viewCursorRect = view->mapFromScene(sceneCursorRect).boundingRect();
                qDebug() << "  Query CursorRectangle: View" << viewCursorRect;
                return QVariant(viewCursorRect);
            }
            return QVariant(QRect());
        }
        case Qt::InputMethodQuery::Font:
        {
            qDebug() << "  Query Font:" << m_font.family();
            return QVariant(m_font);
        }
        case Qt::InputMethodQuery::SelectedText:
        {
            // この例では選択機能は未実装なので空文字列を返す
            qDebug() << "  Query SelectedText: (empty)";
            return QVariant(QString());
        }
        case Qt::InputMethodQuery::SurroundingText:
        {
            // 現在のテキスト全体を返す
            qDebug() << "  Query SurroundingText:" << m_text + m_preeditText;
            return QVariant(m_text + m_preeditText);
        }
        case Qt::InputMethodQuery::AnchorPosition:
        {
            // 選択範囲がない場合、カーソル位置と同じ
            QPointF localAnchorPos(fm.width(m_text.left(m_cursorPosition) + m_preeditText), 0);
            if (scene() && scene()->views().size() > 0) {
                QGraphicsView* view = scene()->views().first();
                QPointF sceneAnchorPos = mapToScene(localAnchorPos);
                QPoint viewAnchorPos = view->mapFromScene(sceneAnchorPos);
                QPoint globalPos = view->viewport()->mapToGlobal(viewAnchorPos);
                qDebug() << "  Query AnchorPosition:" << globalPos;
                return QVariant(globalPos);
            }
            return QVariant(QPoint());
        }
        case Qt::InputMethodQuery::InputMethodHints:
        {
            // テキスト入力アイテムのヒントを返す (例: ImhNone)
            Qt::InputMethodHints hints = Qt::InputMethodHint::ImhNone;
            qDebug() << "  Query InputMethodHints:" << hints;
            return QVariant(hints);
        }
        default:
            break;
    }
    return QGraphicsItem::inputMethodQuery(query); // デフォルトの動作に任せる
}

main.cpp (MyCustomTextItem を使用する場合)

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include "mycustomtextitem.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600);

    QGraphicsView view(&scene);
    view.setWindowTitle("Custom Graphics Item with IME Support");
    view.resize(800, 600);
    view.setRenderHint(QPainter::Antialiasing);

    MyCustomTextItem* customTextItem = new MyCustomTextItem();
    customTextItem->setPos(50, 50);
    customTextItem->setText("Hello, click and type here!");
    scene.addItem(customTextItem);

    // 初期フォーカス設定
    customTextItem->setFocus();

    view.show();

    return a.exec();
}

説明

この例では、QGraphicsTextItem を使用する代わりに、QGraphicsItem を直接継承して、テキストの描画と入力処理をゼロから実装しています。

  • MyCustomTextItem の役割
    • boundingRect(): アイテムの領域を正確に定義します。
    • paint(): テキスト、カーソル、確定前テキストなどを描画します。
    • inputMethodEvent()
      IMEからの確定済みテキスト (commitString()) や確定前テキスト (preeditString()) を受け取って、アイテム内のテキスト状態を更新します。
    • inputMethodQuery()
      これがこの例の主要な部分です。IMEが必要とするカーソル位置、周囲のテキスト、フォントなどの情報を QVariant で返します。座標変換 (mapToScene, view->mapFromScene, view->viewport()->mapToGlobal) が特に重要です。IMEは多くの場合、グローバルスクリーン座標での情報が必要です。
    • focusInEvent(), focusOutEvent(), keyPressEvent(): フォーカス管理や、IMEが処理しない一般的なキー入力(バックスペース、矢印キーなど)を処理します。
  • IME ヒント
    Qt::InputMethodQuery::InputMethodHints で適切なヒントを返すことで、IMEの挙動を最適化できます。
  • 複雑なテキストレイアウト
    QGraphicsTextItem を使用しない場合、テキストの複数行表示、折り返し、選択範囲のハイライト、フォントメトリクスに基づいた正確なカーソル位置計算など、テキストレンダリングの複雑さをすべて自分で処理する必要があります。QFontMetrics, QTextOption, QTextLayout などのクラスが役立ちます。
  • 再描画
    テキストやカーソル位置が変更されたら、update() を呼び出してアイテムを再描画し、変更をユーザーに視覚的に反映させることが重要です。
  • QInputMethodEvent の処理
    inputMethodEvent() は、IMEからの入力テキストを受け取るためのイベントです。event->commitString()event->preeditString() を適切に処理し、アイテムのテキスト内容を更新する必要があります。
  • フォーカス
    IMEは、入力フォーカスを持つウィジェットまたはアイテムに対してのみ問い合わせを行います。setFocus() を呼び出すか、ItemIsFocusable フラグを設定してフォーカスを受け取れるようにすることが不可欠です。
  • 座標変換
    inputMethodQuery()PositionCursorRectangle を返す場合、IMEは通常、グローバルスクリーン座標(またはビューポートのローカル座標)を期待します。QGraphicsItem のローカル座標からシーン座標、さらに QGraphicsView のビューポート座標、そしてグローバル座標へと正確に変換する必要があります。
    • QGraphicsItem::mapToScene(): アイテムのローカル座標をシーン座標に変換。
    • QGraphicsView::mapFromScene(): シーン座標をビューポート(QGraphicsView のクライアント領域)のローカル座標に変換。
    • QWidget::mapToGlobal() (QGraphicsView::viewport() で取得したウィジェットに対して呼び出す): ビューポートのローカル座標をグローバルスクリーン座標に変換。


Qt プログラミングにおける QVariant QGraphicsView::inputMethodQuery() は、QGraphicsView 上で カスタムのテキスト入力機能 を実装し、かつ IME (Input Method Editor) との連携 を行いたい場合に不可欠なメソッドです。しかし、すべての場合にこのメソッドを直接オーバーライドする必要があるわけではありません。

ここでは、QVariant QGraphicsView::inputMethodQuery() を直接扱う以外の代替手段や、その必要性を回避する方法について説明します。

QGraphicsTextItem を利用する

これは最も推奨される、かつ一般的な方法です。

QGraphicsTextItem は、QGraphicsScene 上にリッチテキストやプレーンテキストを表示・編集するための高機能なアイテムです。内部的に QTextDocumentQTextCursor を使用しており、IME との連携(inputMethodQuery() の適切な実装を含む)は Qt フレームワークが自動的に処理してくれます。

  • 欠点
    • QGraphicsTextItem の提供する機能が、完全にカスタムなテキスト表示・編集の要件に合わない場合がある。
    • 非常に特殊な描画ロジックやインタラクションが必要な場合は、それでも不十分な場合がある。
  • 利点
    • IME連携が自動的に機能する。
    • テキスト描画、選択、カーソル管理など、多くのテキスト編集機能が組み込まれている
    • リッチテキスト(HTMLなど)やプレーンテキストに対応。
    • 開発コストが大幅に削減される。

使用例

単に QGraphicsView 上でユーザーがテキストを入力できるようにしたいだけであれば、QGraphicsTextItem を作成し、それを編集可能に設定するだけで十分です。

#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsTextItem>
#include <QApplication>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    QGraphicsTextItem* textItem = new QGraphicsTextItem("ここに入力してください...");
    textItem->setPos(50, 50);
    textItem->setTextWidth(200); // テキスト領域の幅を設定

    // ここが重要: テキストアイテムを編集可能にする
    textItem->setTextInteractionFlags(Qt::TextEditorInteraction);
    textItem->setFlag(QGraphicsItem::ItemIsFocusable); // フォーカス可能にする

    scene.addItem(textItem);

    view.resize(600, 400);
    view.setWindowTitle("QGraphicsTextItem Example");
    view.show();

    // 初期フォーカスを設定して、すぐにタイプできるようにする
    textItem->setFocus();

    return a.exec();
}

この方法では、MyGraphicsView を継承して inputMethodQuery() をオーバーライドする必要はありません。IME連携は QGraphicsTextItem 自身が処理します。

QGraphicsProxyWidget を利用して標準ウィジェットを埋め込む

QGraphicsScene 内で QLineEditQTextEdit といった標準の Qt ウィジェットを使用したい場合、QGraphicsProxyWidget を介してこれらをシーンに埋め込むことができます。

  • 欠点
    • QGraphicsScene 上でのウィジェットの描画が、QGraphicsItem のような独自の描画ロジックとは異なる(ウィジェットの描画システムに依存)。
    • 非常に多数のウィジェットを埋め込むとパフォーマンスが低下する可能性がある。
  • 利点
    • 既存の QLineEditQTextEdit が持つ全ての機能(IME連携、コピー&ペースト、アンドゥ/リドゥなど)をそのまま利用できる
    • 非常に堅牢で、プラットフォームネイティブな入力体験を提供できる。
    • QGraphicsView::inputMethodQuery() のオーバーライドは不要。

使用例

#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsProxyWidget>
#include <QLineEdit>
#include <QTextEdit>
#include <QApplication>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    // QLineEdit を埋め込む例
    QLineEdit* lineEdit = new QLineEdit("プロキシウィジェットのテスト");
    QGraphicsProxyWidget* proxyLineEdit = scene.addWidget(lineEdit);
    proxyLineEdit->setPos(50, 50);
    proxyLineEdit->setFlag(QGraphicsItem::ItemIsMovable); // 動かせるように

    // QTextEdit を埋め込む例
    QTextEdit* textEdit = new QTextEdit("複数行のテキスト\nIMEテスト\n");
    QGraphicsProxyWidget* proxyTextEdit = scene.addWidget(textEdit);
    proxyTextEdit->setPos(50, 100);
    proxyTextEdit->setFlag(QGraphicsItem::ItemIsMovable);

    view.resize(600, 400);
    view.setWindowTitle("QGraphicsProxyWidget Example");
    view.show();

    // フォーカスを設定
    lineEdit->setFocus();

    return a.exec();
}

この方法も、inputMethodQuery() のオーバーライドを必要としません。IME連携は、埋め込まれた QLineEditQTextEdit が自身で処理します。

IMEを使用しない、またはシンプルなキー入力に限定する

もしアプリケーションが多言語入力やIMEの高度な機能を必要とせず、英数字の直接入力のみで十分な場合、あるいは非常にシンプルなテキスト入力のみを想定している場合は、inputMethodQuery() をオーバーライドする必要はありません。

  • 利点
    • 最も実装が簡単。IME連携の複雑さを完全に回避できる。

使用例

QGraphicsItem を継承した独自のアイテムで、keyPressEvent をオーバーライドして直接テキストを処理します。

// MySimpleTextItem.h
#include <QGraphicsItem>
#include <QString>
#include <QKeyEvent>
#include <QPainter>

class MySimpleTextItem : public QGraphicsItem {
public:
    MySimpleTextItem(QGraphicsItem* parent = nullptr) : QGraphicsItem(parent), m_text(""), m_cursorPos(0) {
        setFlag(ItemIsFocusable); // フォーカス可能にする
    }

    QRectF boundingRect() const override {
        QFontMetrics fm(painter->font()); // 仮のフォント
        return fm.boundingRect(m_text).adjusted(-2, -2, 2, 2); // 描画領域を計算
    }

    void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override {
        painter->setPen(Qt::black);
        painter->drawText(0, 0, m_text);

        if (hasFocus()) {
            QFontMetrics fm(painter->font());
            int x = fm.width(m_text.left(m_cursorPos));
            painter->drawLine(x, 0, x, fm.height()); // カーソル描画
        }
    }

protected:
    void keyPressEvent(QKeyEvent *event) override {
        if (event->text().length() > 0 && !event->matches(QKeySequence::StandardKey::Delete) &&
            !event->matches(QKeySequence::StandardKey::Backspace) &&
            !event->matches(QKeySequence::StandardKey::MoveToNextChar) &&
            !event->matches(QKeySequence::StandardKey::MoveToPreviousChar)) {
            m_text.insert(m_cursorPos, event->text());
            m_cursorPos += event->text().length();
            update();
        } else if (event->key() == Qt::Key_Backspace) {
            if (m_cursorPos > 0) {
                m_text.remove(m_cursorPos - 1, 1);
                m_cursorPos--;
                update();
            }
        } else if (event->key() == Qt::Key_Left) {
            if (m_cursorPos > 0) {
                m_cursorPos--;
                update();
            }
        } else if (event->key() == Qt::Key_Right) {
            if (m_cursorPos < m_text.length()) {
                m_cursorPos++;
                update();
            }
        } else {
            QGraphicsItem::keyPressEvent(event); // 未処理のキーイベントを親クラスに渡す
        }
    }

private:
    QString m_text;
    int m_cursorPos;
};

QVariant QGraphicsView::inputMethodQuery() を直接オーバーライドする必要があるのは、以下の非常に特定のシナリオに限られます。

  1. 完全にカスタムなテキスト入力アイテムを作成し、それが QGraphicsTextItemQGraphicsProxyWidget では実現できない独自の描画ロジックやインタラクションを持つ場合。