1文字ずつ履歴を保存する方法

QPlainTextEditなどのエディタークラスにはあらかじめundoredo関数が用意されていて直前の操作をやり直すことができるようになっています。

しかし、履歴はブロック単位で保存されてるので直前の操作をundoを使ってやり直すと行ごと消されてしまいます。

undoする前undoした後
アンドゥする前のエディター アンドゥした後にエディター

もし1文字ずつアンドゥ・リドゥしたい場合はQUndoStackクラスを使って履歴を好きなようにカスタマイズして保存できます。

その方法は

  1. QUndoCommandで履歴のカスタマイズ
  2. QUndoStackからアンドゥ・リドゥ

履歴のカスタマイズ

ここでは1文字ずつ履歴を記録していきたいのでQUndoCommandを拡張してカスタマイズした履歴クラスを作ります。

ここでは以下のようなEditDoCommandというクラスを作りました。

次がそのヘッダファイルです。

#ifndef EDITDOCOMMAND_H
#define EDITDOCOMMAND_H

#include <QUndoCommand>
#include <QTextDocument>
#include <QTextCursor>

/** 履歴を1文字ずつ保存する.*/
class EditDoCommand : public QUndoCommand
{
public:
    EditDoCommand(QTextDocument* document, 
                const QString & removedText, const QString & addedText,
                   int position, QUndoCommand * command = 0);
    ~EditDoCommand(){}

    virtual void undo();
    virtual void redo();

private:
    QTextDocument* document;
        /// テキストエディターが持つドキュメント
    QTextCursor cursor;
        /// 文字の追加・削除を行うカーソル
    QString addedText = "";
        /// 追加されたテキスト
    QString removedText = "";
        /// 削除されたテキスト
    int position;

    bool isFirstRedone = false;
};

#endif // EDITDOCOMMAND_H

このクラスのundoredo関数を実装させることで、それぞれ「元に戻す」処理と「やり直し」の処理を定義することができます。

次がEditDoCommandの定義部分です。

#include "EditDoCommand.h"

EditDoCommand::EditDoCommand(QTextDocument* document, 
                const QString & removedText, const QString & addedText,
                int position, QUndoCommand * command)
    : QUndoCommand(command)
{
    this->document = document;
    this->cursor = QTextCursor(document);
    this->addedText = addedText;
    this->removedText = removedText;
    this->position = position;
}

/** アンドゥ*/
void EditDoCommand::undo()
{
    cursor.setPosition(position);
    if(!removedText.isEmpty()){
        cursor.insertText(removedText);
    }else{
        cursor.deleteChar();
    }
}

/** リドゥ*/
void EditDoCommand::redo()
{
    /// 初めてredoされたときは何もしない
    if(!isFirstRedone){
        isFirstRedone = true;
        return;
    }
    
    cursor.setPosition(position);
    if(!removedText.isEmpty()){
        cursor.deleteChar();
    }else{
        cursor.insertText(addedText);
    }
}

上のようにエディターのQTextDocumentからカーソルを取得してundoredoから直接文字を削除したり挿入したりしています。

QUndoStackから履歴を記録

履歴部分をカスタマイズできたので次に履歴を記録する部分を作っていきます。

履歴の記録にはQUndoStackが使われ、このクラスはQUndoCommandを拡張した履歴クラスを順番に保存して管理する機能がついています。

履歴の挿入にはpush関数が使われ、履歴を1つ戻るのにundo関数、1つ進むのにredo関数を使います。

ではこれを使ってエディターの履歴をカスタマイズしてみます。

まず次がエディターのヘッダ部分です。

#ifndef MYEDITOR_H
#define MYEDITOR_H

class MyEditor : public QPlainTextEdit{
    Q_OBJECT
    
public:
    explicit MyEditor(QWidget* parent = 0);
    ~MyEditor();
    
    virtual void keyPressEvent(QKeyEvent *event);
    
public slots:
    void undo();
    void redo();
    
private:
    QUndoStack * editDoStack;
        /// 履歴スタック
};

#endif // MYEDITOR_H

そして次がエディターの定義です。

#include <MyEditor.h>

#include "EditDoCommand.h"

MyEditor::MyEditor(QWidget *parent)
    : QPlainTextEdit(parent),
      editDoStack(new QUndoStack(this))
{
    setUndoRedoEnabled(false);
        /// 標準のアンドゥ・リドゥを無効にする
}

MyEditor::~MyEditor()
{
    delete editDoStack;
    editDoStack = 0;
}

/** 元に戻す(アンドゥ)*/
void MyEditor::undo()
{
    editDoStack->undo();
}

/** やり直し(リドゥ)*/
void MyEditor::redo()
{
    editDoStack->redo();
}

void MyEditor::keyPressEvent(QKeyEvent* event)
{
    if(!QChar::isPrint(key) 
            && (key != Qt::Key_Backspace && key != Qt::Key_Return 
            && key != Qt::Key_Enter && key != Qt::Key_Tab)){
        QPlainTextEdit::keyPressEvent(event);
        return;
    }
    
    QTextCursor cursor = this->textCursor();
    if(key == Qt::Key_Backspace){
        QString removedText = document().characterAt(cursor.position - 1);
        editDoStack.push(new EditDoCommand(document(), removedText, "",
                        cursor.position - 1));
            /// 履歴追加
        QPlainTextEdit::keyPressEvent(event);
    }else{
        QPlainTextEdit::keyPressEvent(event);
        QString addedText = document().characterAt(cursor.position - 1);
        editDoStack.push(new EditDoCommand(document(), "", addedText,
                        cursor.position - 1));
            /// 履歴追加
    }
}

履歴は戻るだけではなく、進めることも出来なくてはならないので削除の時と普通のキーが入力されたときとで処理を分けています。

また処理が複雑になってしまうのでこの例ではdeleteキーは無視しています。

そしてエディタークラスのundoまたはredo関数が呼び出されたら直前の操作を元に戻すかやり直せるようにしています。

キー入力と同じようにカットやペーストもQUndoCommandを工夫すれば記録できます。

関連項目
プライバシーポリシー