1文字ずつ履歴を保存する方法
QPlainTextEditなどのエディタークラスにはあらかじめundoやredo関数が用意されていて直前の操作をやり直すことができるようになっています。
しかし、履歴はブロック単位で保存されてるので直前の操作をundoを使ってやり直すと行ごと消されてしまいます。
undoする前 | undoした後 |
---|---|
もし1文字ずつアンドゥ・リドゥしたい場合は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
このクラスのundo、redo関数を実装させることで、それぞれ「元に戻す」処理と「やり直し」の処理を定義することができます。
次が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からカーソルを取得してundo・redoから直接文字を削除したり挿入したりしています。
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を工夫すれば記録できます。