QTextEditのコンテキストメニューを自由自在に:createStandardContextMenu()以外の選択肢

2025-05-27

QTextEdit::createStandardContextMenu() は、Qt の QTextEdit クラスが提供するメンバ関数で、QTextEdit 上で右クリックしたときに表示される標準的なコンテキストメニューを作成して返します。

QTextEdit とは

まず、QTextEdit は、Qt フレームワークにおけるリッチテキストエディタウィジェットです。テキストの表示、編集、書式設定(太字、斜体、色など)をサポートします。

コンテキストメニューとは

コンテキストメニューとは、特定の要素(この場合は QTextEdit 上のテキストなど)を右クリックしたときに表示される、その要素に関連する操作のリストのことです。例えば、テキストエディタであれば「切り取り」「コピー」「貼り付け」「元に戻す」といった操作が一般的です。

createStandardContextMenu() の役割

QTextEdit::createStandardContextMenu() 関数は、この標準的なコンテキストメニューをプログラム的に生成するためのものです。この関数を呼び出すと、以下のような一般的なテキスト編集操作を含む QMenu オブジェクトが返されます。

  • すべて選択 (Select All)
  • 削除 (Delete)
  • 貼り付け (Paste)
  • コピー (Copy)
  • 切り取り (Cut)
  • やり直し (Redo)
  • 元に戻す (Undo)

使い方

通常、この関数は QTextEditcontextMenuEvent() メソッドをオーバーライドしたり、customContextMenuRequested シグナルを処理する際に使用されます。


#include <QTextEdit>
#include <QMenu>
#include <QContextMenuEvent>

class MyTextEdit : public QTextEdit
{
public:
    MyTextEdit(QWidget *parent = nullptr) : QTextEdit(parent) {}

protected:
    void contextMenuEvent(QContextMenuEvent *event) override {
        // 標準のコンテキストメニューを作成
        QMenu *menu = createStandardContextMenu();

        // 必要に応じて、独自のメニュー項目を追加する
        // menu->addAction("カスタムアクション");

        // コンテキストメニューを表示
        menu->exec(event->globalPos());

        // メニューの所有権は呼び出し元に渡されるため、適切に削除する
        delete menu;
    }
};

// ... メインウィンドウなどでMyTextEditを使用 ...

上記の例では、MyTextEdit という QTextEdit を継承したクラスを作成し、contextMenuEvent() をオーバーライドしています。このイベントが発生した際に、createStandardContextMenu() を呼び出して標準メニューを取得し、必要であれば追加のアクションを追加した上で、イベント発生位置 (event->globalPos()) にメニューを表示しています。

メリット

  • 拡張性
    返された QMenu オブジェクトに対して、独自のアクションを追加したり、既存のアクションを無効にしたりすることで、カスタマイズが可能です。
  • 一貫性
    アプリケーション内で一貫したテキスト編集体験を提供できます。
  • 手軽さ
    標準的なテキスト編集操作のメニューを簡単に作成できます。
  • この関数は、QTextEdit のコンテキストメニューイベント処理の起点となることが多く、そのまま使われることもあれば、独自のメニューに標準メニューの一部を組み込む形で利用されることもあります。
  • createStandardContextMenu() が返す QMenu オブジェクトは、呼び出し元が所有権を持つことになります。したがって、メニューを表示した後は、メモリリークを防ぐために delete する必要があります。


メニューが表示されない、または期待通りに表示されない

共通のエラー

  • setContextMenuPolicy() が適切に設定されていない
    • QTextEditcontextMenuPolicy プロパティが Qt::DefaultContextMenu または Qt::CustomContextMenu に設定されていることを確認してください。Qt::NoContextMenu に設定されていると、右クリックしてもメニューは表示されません。
  • メニューの exec() が呼び出されていない
    • QMenu::exec() メソッドを呼び出さない限り、作成したメニューは表示されません。
  • event->globalPos() の使用ミス
    • メニューを表示する位置を指定する際、ローカル座標(ウィジェット内)ではなく、グローバル座標(スクリーン全体)で指定する必要があります。QContextMenuEvent::globalPos() を使用しないと、メニューが意図しない場所に表示されるか、全く表示されないことがあります。
  • contextMenuEvent がオーバーライドされていない、または適切に処理されていない
    • QTextEdit の標準の動作では、右クリックしてもコンテキストメニューは自動的に表示されません。contextMenuEvent() をオーバーライドし、その中で createStandardContextMenu() を呼び出してメニューを表示する必要があります。

トラブルシューティング

  • this->setContextMenuPolicy(Qt::DefaultContextMenu); または this->setContextMenuPolicy(Qt::CustomContextMenu); をコンストラクタなどで設定してみてください。
  • qDebug() を使って、contextMenuEvent() が実際に呼び出されているか、event->globalPos() が適切な座標を返しているかを確認してください。
  • contextMenuEvent() がオーバーライドされていることを確認し、その中で createStandardContextMenu() が呼び出されているか、そしてその戻り値に対して exec(event->globalPos()) が呼び出されているかを確認してください。

メモリリーク(QMenu オブジェクトのリーク)

  • QMenu オブジェクトの delete 忘れ
    • createStandardContextMenu() は、新しく QMenu オブジェクトをヒープに作成して返します。このオブジェクトの所有権は呼び出し側に移るため、メニューを表示し終わったら必ず delete してメモリを解放する必要があります。これを忘れると、右クリックするたびに新しい QMenu オブジェクトが作成され、メモリリークが発生します。
  • QMenu *menu = createStandardContextMenu(); の後で、menu->exec(...) を呼び出した後、必ず delete menu; を記述してください。
    void MyTextEdit::contextMenuEvent(QContextMenuEvent *event) {
        QMenu *menu = createStandardContextMenu();
        // 必要に応じてメニュー項目を追加・編集
        menu->exec(event->globalPos());
        delete menu; // これが重要!
    }
    

メニューのアクションが機能しない、または表示がおかしい

  • CSS/スタイルシートによる影響
    • アプリケーション全体やウィジェットに適用されているスタイルシートが、コンテキストメニューの見た目に影響を与えている可能性があります。
  • アクションの有効/無効状態が適切に更新されていない
    • 例えば、テキストが選択されていないのに「コピー」アクションが有効になっていたり、クリップボードに内容がないのに「貼り付け」アクションが有効になっていたりすることがあります。標準アクションは通常、QTextEdit の状態に基づいて自動的に有効/無効が切り替わりますが、複雑なケースやカスタムアクションでは手動での制御が必要になる場合があります。
  • カスタムアクションを追加したが、シグナルとスロットが接続されていない
    • createStandardContextMenu() で作成された標準アクションは自動的に QTextEdit の適切なスロットに接続されますが、独自に追加した QAction は、明示的に QAction::triggered シグナルを QTextEdit の適切なスロット(例: cut(), copy(), paste() などのパブリックスロット)またはカスタムスロットに接続する必要があります。
  • メニューの表示がおかしい場合は、アプリケーションのスタイルシートを確認し、必要であればデバッグのために一時的に無効にしてみてください。
  • アクションの有効/無効状態を制御する必要がある場合、QAction::setEnabled(bool) メソッドを使用します。例えば、テキストが選択されているかどうかで「コピー」アクションの有効/無効を切り替えるなどです。
  • カスタムアクションを追加した場合、connect() 関数を使ってシグナルとスロットを正しく接続しているか確認してください。
  • ゼロからメニューを作成する手間
    • createStandardContextMenu() を使わずに一から QMenu を作成すると、一般的なテキスト編集操作のアクションをすべて手動で作成し、QTextEdit のスロットに接続する必要があります。これは手間がかかります。
  • 推奨されるアプローチ
    • 基本的に createStandardContextMenu() を利用し、その戻り値に対して追加のアクションを追加したり、既存のアクションを削除・変更したりするのが最も効率的で堅牢な方法です。
    • 例えば、特定の機能にアクセスさせたくない場合、menu->removeAction(menu->findChild<QAction*>("undoActionName")); のように、特定のアクションを削除することも可能です(アクション名が分かっている場合)。


基本的な使用例:標準のコンテキストメニューを表示する

これは最も基本的な使用方法で、QTextEdit を継承し、contextMenuEvent() をオーバーライドして標準メニューを表示します。

mytextedit.h

#ifndef MYTEXTEDIT_H
#define MYTEXTEDIT_H

#include <QTextEdit>
#include <QMenu> // QMenu を使用するために必要
#include <QContextMenuEvent> // QContextMenuEvent を使用するために必要

class MyTextEdit : public QTextEdit
{
    Q_OBJECT // シグナルとスロットを使用する場合に必要

public:
    explicit MyTextEdit(QWidget *parent = nullptr);

protected:
    // 右クリックイベントを処理するためのオーバーライド
    void contextMenuEvent(QContextMenuEvent *event) override;
};

#endif // MYTEXTEDIT_H

mytextedit.cpp

#include "mytextedit.h"
#include <QDebug> // デバッグ出力用

MyTextEdit::MyTextEdit(QWidget *parent) : QTextEdit(parent)
{
    // コンテキストメニューポリシーをカスタムに設定
    // これにより、contextMenuEvent が発生するようになります。
    // Qt::DefaultContextMenu でも動作しますが、CustomContextMenu を使うと
    // イベントが確実にアプリケーションに送られます。
    this->setContextMenuPolicy(Qt::CustomContextMenu);

    // デフォルトテキスト
    this->setPlainText("ここにテキストを入力し、右クリックしてください。\n");
    this->append("これは MyTextEdit のテストです。");
}

void MyTextEdit::contextMenuEvent(QContextMenuEvent *event)
{
    qDebug() << "contextMenuEvent が発生しました。";

    // QTextEdit の標準コンテキストメニューを作成
    // この QMenu オブジェクトはヒープに作成されるため、使用後に削除が必要です。
    QMenu *menu = createStandardContextMenu();

    // コンテキストメニューをイベント発生位置 (グローバル座標) に表示
    // exec() はメニューが閉じられるまでブロックします。
    menu->exec(event->globalPos());

    // メモリリークを防ぐため、作成した QMenu オブジェクトを削除
    delete menu;
    qDebug() << "メニューが閉じられ、メモリが解放されました。";
}

main.cpp

#include <QApplication>
#include <QMainWindow>
#include "mytextedit.h"

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

    QMainWindow window;
    MyTextEdit *textEdit = new MyTextEdit(&window);
    window.setCentralWidget(textEdit);
    window.setWindowTitle("QTextEdit 標準コンテキストメニューの例");
    window.resize(600, 400);
    window.show();

    return a.exec();
}

解説

  1. MyTextEdit クラスで QTextEdit を継承します。
  2. contextMenuEvent(QContextMenuEvent *event) をオーバーライドします。この関数は、ウィジェット上で右クリックイベントが発生したときにQtによって呼び出されます。
  3. createStandardContextMenu() を呼び出して、標準の「元に戻す」「コピー」「貼り付け」などのアクションを含む QMenu オブジェクトを取得します。
  4. menu->exec(event->globalPos()); で、イベントが発生したスクリーンの位置(グローバル座標)にメニューを表示します。
  5. 非常に重要
    delete menu; を呼び出して、ヒープに作成された QMenu オブジェクトのメモリを解放します。これを忘れるとメモリリークが発生します。
  6. コンストラクタで setContextMenuPolicy(Qt::CustomContextMenu); を設定していますが、Qt::DefaultContextMenu でもこのイベントは発生します。Qt::NoContextMenu の場合は発生しません。

標準メニューにカスタムアクションを追加する

既存の標準メニューに、独自のアクションを追加する例です。

mytextedit.h (変更なし)

mytextedit.cpp

#include "mytextedit.h"
#include <QDebug>
#include <QAction> // QAction を使用するために必要
#include <QMessageBox> // メッセージボックス表示用

MyTextEdit::MyTextEdit(QWidget *parent) : QTextEdit(parent)
{
    this->setContextMenuPolicy(Qt::CustomContextMenu);
    this->setPlainText("ここにテキストを入力し、右クリックしてください。\n");
    this->append("このメニューにはカスタムアクションが追加されています。");
}

void MyTextEdit::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu *menu = createStandardContextMenu();

    // --- ここからカスタムアクションの追加 ---
    // セパレータを追加して、標準アクションとカスタムアクションを区切る
    menu->addSeparator();

    // カスタムアクションの作成
    QAction *myCustomAction = new QAction("カスタムアクション", this);
    // アクションがトリガーされたときに呼び出すスロットを接続
    // lambda 関数を使用
    connect(myCustomAction, &QAction::triggered, this, [this]() {
        QMessageBox::information(this, "カスタムアクション", "カスタムアクションが実行されました!");
        qDebug() << "カスタムアクション実行";
    });

    // メニューにカスタムアクションを追加
    menu->addAction(myCustomAction);

    // テキスト選択時にのみ有効になるカスタムアクション
    if (this->textCursor().hasSelection()) {
        QAction *showSelectionAction = new QAction("選択テキストを表示", this);
        connect(showSelectionAction, &QAction::triggered, this, [this]() {
            QMessageBox::information(this, "選択テキスト", "選択されたテキスト: " + this->textCursor().selectedText());
            qDebug() << "選択テキスト: " << this->textCursor().selectedText();
        });
        menu->addAction(showSelectionAction);
    }
    // --- ここまでカスタムアクションの追加 ---

    menu->exec(event->globalPos());
    delete menu; // メモリ解放を忘れずに
}

main.cpp (変更なし)

解説

  1. createStandardContextMenu() で標準メニューを取得した後、menu->addSeparator(); で区切り線を追加しています。
  2. QAction *myCustomAction = new QAction("カスタムアクション", this); で新しいアクションを作成します。親オブジェクトを this (MyTextEdit) に指定することで、MyTextEdit が削除されるときにアクションも自動的に削除されます。
  3. connect(myCustomAction, &QAction::triggered, ...) を使って、このアクションがクリックされたときに実行されるスロット(この場合はラムダ関数)を接続します。ここでは簡単なメッセージボックスを表示しています。
  4. menu->addAction(myCustomAction); で、作成したカスタムアクションをメニューに追加します。
  5. さらに、this->textCursor().hasSelection() をチェックして、テキストが選択されている場合にのみ「選択テキストを表示」という別のアクションを追加しています。これにより、メニューの動的な変更が可能です。

標準メニューの特定のアクションを無効にしたり、完全に削除したりする方法です。

mytextedit.h (変更なし)

mytextedit.cpp

#include "mytextedit.h"
#include <QDebug>
#include <QAction>

MyTextEdit::MyTextEdit(QWidget *parent) : QTextEdit(parent)
{
    this->setContextMenuPolicy(Qt::CustomContextMenu);
    this->setPlainText("ここにテキストを入力し、右クリックしてください。\n");
    this->append("「元に戻す」アクションは無効化され、「切り取り」アクションは削除されています。");
}

void MyTextEdit::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu *menu = createStandardContextMenu();

    // --- ここから標準アクションの変更 ---

    // 「元に戻す」アクションを無効化する例
    // QTextEdit の undo() スロットに対応するアクションを見つける
    QList<QAction*> actions = menu->actions();
    for (QAction *action : actions) {
        // QTextEdit の標準アクションは、対応するスロット名に基づいてオブジェクト名が設定されることが多い
        // ここでは一般的な "undo" アクションを探す
        if (action->text().contains("元に戻す") || action->text().contains("Undo")) { // ロケールに依存するため注意
            action->setEnabled(false); // アクションを無効化
            qDebug() << "「元に戻す」アクションを無効化しました。";
            break;
        }
    }

    // 「切り取り」アクションを削除する例
    // removeAction() は QAction のポインタを必要とする
    // 特定のアクションを直接取得するのが難しい場合、そのアクションのテキストなどで探す
    QAction *cutAction = nullptr;
    for (QAction *action : actions) {
        if (action->text().contains("切り取り") || action->text().contains("Cut")) {
            cutAction = action;
            break;
        }
    }
    if (cutAction) {
        menu->removeAction(cutAction); // メニューからアクションを削除
        qDebug() << "「切り取り」アクションを削除しました。";
    }

    // --- ここまで標準アクションの変更 ---

    menu->exec(event->globalPos());
    delete menu; // メモリ解放を忘れずに
}

main.cpp (変更なし)

  1. menu->actions() を使って、メニュー内のすべてのアクションのリストを取得します。
  2. 目的のアクションが見つかったら、action->setEnabled(false); で無効にしたり、menu->removeAction(action); でメニューから削除したりします。
    • removeAction() はメニューからアクションを削除するだけで、アクションオブジェクト自体は削除しません。アクションオブジェクトの親が適切に設定されていれば、その親が削除されるときに自動的に削除されます。そうでなければ、手動で delete する必要がある場合があります。createStandardContextMenu() が返す標準アクションの親は通常メニュー自身なので、メニューを delete する際にアクションも削除されます。


代替方法の目的

代替方法を検討する主な目的は以下の通りです。

  1. 完全にカスタムなメニューを作成する
    createStandardContextMenu() が提供する標準のアクションではなく、ゼロから独自のメニュー項目を定義したい場合。
  2. イベント処理の柔軟性
    contextMenuEvent() をオーバーライドする以外の方法でコンテキストメニューの表示をトリガーしたい場合。
  3. 特定の要件への対応
    標準のアクションの動作を完全に変更したり、複数のウィジェットで共通のメニューロジックを再利用したりする場合。

QTextEdit::customContextMenuRequested シグナルを使用する

これは、contextMenuEvent() をオーバーライドする最も一般的で推奨される代替手段です。ウィジェットの contextMenuPolicyQt::CustomContextMenu に設定することで、右クリック時に customContextMenuRequested(const QPoint &pos) シグナルが発せられます。このシグナルを捕捉して、任意のスロットでメニューを構築・表示します。

  • ラムダ式との相性
    モダンなQtのシグナル・スロット接続で、ラムダ式と組み合わせて手軽に処理を記述できます。
  • 複数の接続
    複数のスロットを同じシグナルに接続したり、同じスロットを複数のウィジェットのシグナルに接続したりできます。
  • 関心の分離
    メニュー表示ロジックを、ウィジェットクラスの外部(例: メインウィンドウクラスや別のヘルパークラス)に分離できます。
  • イベントフィルタリングの不要
    contextMenuEvent() のように、イベントを適切に受け取ったかどうかを判断する必要がありません。

コード例

mytextedit.h

#ifndef MYTEXTEDIT_H
#define MYTEXTEDIT_H

#include <QTextEdit>
#include <QPoint> // QPoint を使用するために必要

class MyTextEdit : public QTextEdit
{
    Q_OBJECT

public:
    explicit MyTextEdit(QWidget *parent = nullptr);

signals:
    // このクラスは customContextMenuRequested シグナルを発しますが、
    // QTextEdit 自体がこのシグナルを持っているので、明示的な宣言は不要です。
    // convenience: void customContextMenuRequested(const QPoint &pos);

private slots:
    // customContextMenuRequested シグナルを処理するスロット
    void showCustomMenu(const QPoint &pos);
};

#endif // MYTEXTEDIT_H

mytextedit.cpp

#include "mytextedit.h"
#include <QMenu>
#include <QAction>
#include <QMessageBox>
#include <QDebug>

MyTextEdit::MyTextEdit(QWidget *parent) : QTextEdit(parent)
{
    // customContextMenuRequested シグナルを発するようにポリシーを設定
    this->setContextMenuPolicy(Qt::CustomContextMenu);

    // シグナルとスロットを接続
    connect(this, &MyTextEdit::customContextMenuRequested,
            this, &MyTextEdit::showCustomMenu);

    this->setPlainText("ここにテキストを入力し、右クリックしてください。\n");
    this->append("このメニューは customContextMenuRequested シグナルで表示されます。");
}

void MyTextEdit::showCustomMenu(const QPoint &pos)
{
    qDebug() << "customContextMenuRequested シグナルが発せられました。";

    QMenu menu(this); // スタック上に QMenu を作成 (ヒープでも可)

    // 標準のアクションを追加したい場合、QTextEdit のパブリックスロットを利用する
    // 例: 「元に戻す」アクション
    QAction *undoAction = menu.addAction("元に戻す");
    connect(undoAction, &QAction::triggered, this, &QTextEdit::undo);
    undoAction->setEnabled(this->document()->isUndoAvailable()); // アクションの有効/無効制御

    // 例: 「コピー」アクション
    QAction *copyAction = menu.addAction("コピー");
    connect(copyAction, &QAction::triggered, this, &QTextEdit::copy);
    copyAction->setEnabled(this->textCursor().hasSelection()); // アクションの有効/無効制御

    menu.addSeparator(); // 区切り線

    // カスタムアクションの追加
    QAction *myCustomAction = menu.addAction("カスタムアクション(シグナル経由)");
    connect(myCustomAction, &QAction::triggered, this, []() {
        QMessageBox::information(nullptr, "カスタムアクション", "カスタムアクションが実行されました!");
        qDebug() << "カスタムアクション実行 (シグナル経由)";
    });

    // グローバル座標に変換してメニューを表示
    // pos はウィジェットローカル座標なので、mapToGlobal() で変換する
    menu.exec(this->mapToGlobal(pos));

    // スタック上のオブジェクトなので delete は不要
}

main.cpp (変更なし)

解説

  1. setContextMenuPolicy(Qt::CustomContextMenu) を設定します。
  2. connect(this, &MyTextEdit::customContextMenuRequested, this, &MyTextEdit::showCustomMenu); で、シグナルとスロットを接続します。
  3. スロット showCustomMenu 内で、QMenu オブジェクトを新しく作成します(スタックでもヒープでも可)。
  4. 各アクションを手動で作成し、対応する QTextEdit のパブリックスロット(undo(), copy() など)に接続します。これは createStandardContextMenu() が裏で行っていることを明示的に行うことに相当します。
  5. アクションの有効/無効状態も、必要に応じて手動で設定します(例: undoAction->setEnabled(this->document()->isUndoAvailable());)。
  6. menu.exec(this->mapToGlobal(pos)); で、イベントのローカル座標 pos をグローバル座標に変換してメニューを表示します。
  • メニュー構築のロジックをよりシンプルに保てる。
  • アクションがウィジェットのプロパティとして管理されるため、他の場所から参照・操作しやすい。

mytextedit.h

#ifndef MYTEXTEDIT_H
#define MYTEXTEDIT_H

#include <QTextEdit>
#include <QPoint>
#include <QAction> // QAction を使用するために必要

class MyTextEdit : public QTextEdit
{
    Q_OBJECT

public:
    explicit MyTextEdit(QWidget *parent = nullptr);

private slots:
    void showCustomMenu(const QPoint &pos);
    void handleCustomAction(); // カスタムアクションのスロット

private:
    QAction *m_customAction; // カスタムアクションをメンバ変数として保持
};

#endif // MYTEXTEDIT_H

mytextedit.cpp

#include "mytextedit.h"
#include <QMenu>
#include <QMessageBox>
#include <QDebug>

MyTextEdit::MyTextEdit(QWidget *parent) : QTextEdit(parent)
{
    this->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(this, &MyTextEdit::customContextMenuRequested,
            this, &MyTextEdit::showCustomMenu);

    this->setPlainText("ここにテキストを入力し、右クリックしてください。\n");
    this->append("このメニューのアクションは事前に定義されています。");

    // --- ここでカスタムアクションを定義し、QTextEdit に追加 ---
    m_customAction = new QAction("My 事前定義カスタムアクション", this);
    connect(m_customAction, &QAction::triggered, this, &MyTextEdit::handleCustomAction);
    this->addAction(m_customAction); // QTextEdit にアクションを追加

    // 標準アクションも必要なら追加(ただし、これらは通常、ツールバーやメニューバーで使用される)
    // 例: this->addAction(this->cutAction());
    // QTextEdit のパブリックスロットに対応する QAction を取得する方法は createStandardContextMenu() とは異なる
}

void MyTextEdit::handleCustomAction()
{
    QMessageBox::information(this, "カスタムアクション", "事前定義されたカスタムアクションが実行されました!");
    qDebug() << "事前定義カスタムアクション実行";
}

void MyTextEdit::showCustomMenu(const QPoint &pos)
{
    qDebug() << "customContextMenuRequested シグナルが発せられました。";

    QMenu menu(this);

    // QTextEdit が持つすべてのアクションを追加 (例えば cutAction(), copyAction() など)
    // ただし、これらは QWidget::actions() で取得される一般的なアクションであり、
    // テキスト編集用の特定のアクションとは限らないため、注意が必要
    // QTextEdit::createStandardContextMenu() が返すアクションとは性質が異なる
    // 実際には、QTextEdit の組み込みアクションは QAction *cutAction() のようにメソッドで提供される

    // よく使うテキスト編集アクションを直接追加
    menu.addAction(this->undoAction());
    menu.addAction(this->redoAction());
    menu.addSeparator();
    menu.addAction(this->cutAction());
    menu.addAction(this->copyAction());
    menu.addAction(this->pasteAction());
    menu.addAction(this->deleteAction());
    menu.addSeparator();
    menu.addAction(this->selectAction()); // selectAll を返す

    menu.addSeparator();
    menu.addAction(m_customAction); // 事前定義したカスタムアクションを追加

    menu.exec(this->mapToGlobal(pos));
}

解説

  1. コンストラクタでカスタムアクション m_customAction を作成し、this->addAction(m_customAction);MyTextEdit に追加しています。
  2. showCustomMenu スロットでは、QTextEdit が提供する undoAction(), cutAction(), copyAction() などのメソッドを使って、既に内部で接続済みのアクションオブジェクトを取得し、それをメニューに追加しています。これらのアクションは QTextEdit の組み込み機能と直接連携しています。
  3. m_customAction もメニューに追加しています。

QMenu を完全に手動で構築し、アクションのロジックも全て自分で実装する

これは最も柔軟ですが、最も手間がかかる方法です。QTextEdit::createStandardContextMenu() が提供するアクションを一切使わず、ゼロからすべてのアクションを定義し、それぞれの動作を自分で実装します。

  • 特定の要件への対応
    非常に特殊なテキスト編集操作を実装する必要がある場合に有効です。
  • 完全な制御
    メニューの外観、表示されるアクション、各アクションの動作を完全に制御できます。

デメリット

  • 再利用性の低下
    同じロジックを他の場所で再利用しにくい。
  • 手間と複雑さ
    標準的な「コピー」「貼り付け」などの機能も自分で実装する必要があり、エラーが発生しやすい。

コード例(抜粋)

// contextMenuEvent または customContextMenuRequested スロット内
void MyTextEdit::showCustomMenu(const QPoint &pos)
{
    QMenu menu(this);

    // 「カスタムコピー」アクションを定義
    QAction *myCopyAction = menu.addAction("カスタムコピー");
    connect(myCopyAction, &QAction::triggered, this, []() {
        // QTextEdit::copy() を呼び出す代わりに、独自のコピーロジックを実装
        QString selectedText = qApp->focusWidget()->findChild<QTextEdit*>()->textCursor().selectedText();
        QClipboard *clipboard = QApplication::clipboard();
        clipboard->setText(selectedText.toUpper()); // 例: コピー時に大文字にする
        qDebug() << "カスタムコピー実行: " << selectedText;
    });
    myCopyAction->setEnabled(this->textCursor().hasSelection());


    // 「カスタム貼り付け」アクションを定義
    QAction *myPasteAction = menu.addAction("カスタム貼り付け");
    connect(myPasteAction, &QAction::triggered, this, []() {
        QClipboard *clipboard = QApplication::clipboard();
        QString clipboardText = clipboard->text();
        qApp->focusWidget()->findChild<QTextEdit*>()->textCursor().insertText(clipboardText.toLower()); // 例: 貼り付け時に小文字にする
        qDebug() << "カスタム貼り付け実行: " << clipboardText;
    });
    // クリッピングボードにテキストがあるか確認
    myPasteAction->setEnabled(!QApplication::clipboard()->text().isEmpty());


    menu.addSeparator();

    // 他のカスタムアクション...

    menu.exec(this->mapToGlobal(pos));
}
  • 非常に低レベルな制御が可能ですが、特別な理由がない限り推奨されません
  • このアプローチでは、QTextEdit::copy()QTextEdit::paste() のような既存のメソッドを呼び出すのではなく、クリップボードへのアクセス(QApplication::clipboard())やカーソル操作(QTextCursor)を直接行って、独自のコピー/貼り付けロジックを実装しています。
  • QMenu を完全に手動で構築し、アクションのロジックも全て自分で実装する」方法は、既存の QTextEdit の編集機能を大幅に変更したい、または全く異なる動作をさせたいという非常に特殊な要件がある場合にのみ検討してください。
  • 最も推奨されるのは「QTextEdit::customContextMenuRequested シグナルを使用する」方法です。
    • 関心の分離が可能で、コードが整理されやすい。
    • 標準アクションとカスタムアクションを混ぜ合わせるのに適しています。
    • createStandardContextMenu() を使用しつつ、柔軟性も維持したい場合に最適です。