Qtブログ(日本語)

Qt Quick テキスト編集機能の向上

作成者: Qt Group 日本オフィス|Aug 13, 2024 8:44:31 AM
本稿は「Text editing improvements in Qt Quick」の抄訳です。
 

Qt QuickとQt Quick Controlsをより多くのアプリケーションでQt Widgetsの適切な代替品とするために、多くのアプリケーションで必要とされていること、また、テキストエディタはデモアプリケーションの一般的なタイプであり、長年にわたってさまざまな方法で書き換えられる傾向にあることから、テキストエディタのユースケースを改善の必要があるものとして特定しました。

ドキュメントオブジェクトのアクセス

TextEdit(またはその派生クラスであるTextArea)のインスタンスは、レンダリングおよびインタラクティブな編集を行うための「モデル」としてQTextDocumentを使用します。Qt 5 の初期段階で、textDocumentプロパティが追加され、内部で作成された QTextDocument インスタンスをアプリケーションの C++ コードに公開する方法が提供されました。最初の使用例は、QSyntaxHighlighterをインストール可能にすることでした。

しかし、ユーザーからは、内部で作成されたインスタンスにアクセスできるだけでなく、それを置き換えられるようにしてほしいという要望が寄せられていました。 これを実現するために、古いプライベートなサブクラスである QQuickTextDocumentWithImageResources を廃止し、リソースのロード(例えば、http経由でロードされるインライン画像)を処理する別の方法を見つける必要がありました。QTextDocument::loadResource() は、親オブジェクトで QVariant loadResource(int, QUrl)というシグネチャを持つメソッドを探します。これに従って、QQuickTextEdit::loadResource() が新たに実装され、デフォルトでは、TextEdit が自身のドキュメントの親となります。

QQuickTextDocument::setTextDocument() は Qt 6.7 で新たに導入されたものです。ドキュメントオブジェクトを置き換える場合、リモートリソースの読み込みが必要であれば、その処理は開発者の責任となります。リモートリソースの読み込みは、親オブジェクトの loadResource() 関数、resourceProvider()、または独自の QTextDocument サブクラスの loadResource() をオーバーライドすることで実行できます。

読み込みと保存

TextEdit.textDocumentはすでに存在していたため、返されるQQuickTextDocumentオブジェクトは、新しいQML向けAPIを追加するのに最適な場所でした。

QMLでメディアファイルを読み込む際のこれまでのパターンは、URLを受け取るsourceプロパティを使用することでした。そこで、QQuickTextDocumentこのプロパティを追加し、Image.sourceを設定するのと同じようにTextEdit.textDocument.sourceを設定できるようにしました。

これまで、QMLにはファイルを書き込むためのAPIはほとんどありませんでした。(例外として、Item.grabToImage()saveToFile()関数を持つオブジェクトを返します。これは、アプリケーション内でスクリーンショットを保存する際に便利です。) 将来的には、一般的なファイル入出力APIが必要になるかもしれません。しかし、現時点ではまだそのようなAPIは存在せず、ほぼすべてのテキストエディタがファイルを保存できる必要があります。理想的には、毎回定型C++コードを記述する必要がないことです。そのため、現時点ではTextDocument.save()saveAs()の2つの関数を追加しました。

この新しい API は、より汎用的なファイル I/O API に置き換えるかどうかを決定するまでは、技術プレビューとなります。 ウィジェットアプリケーションでは、QTextDocumentの生のコンテンツを、toHtml()toMarkdown()などのアクセサメソッドの1つから取得し、それをQFileで書き込む必要があります。おそらく、QML APIでも同様の方法で動作するでしょう。ソースプロパティを使用して読み込みを行うパターンは、比較すると特に簡潔で宣言的であるように思われます。しかし、save()saveAs() APIが、すべてのユーザーのユースケースに対して十分に安全で柔軟であるかどうかについては懸念があります。 この件についてのご意見をお待ちしております。

すぐに話題に上ったことのひとつは、ロード中のテキストからデータを抽出したり、保存中の内容を修正したりする必要がある場合、例えばMarkdownファイルのYAMLメタデータを処理する場合などです。TextDocument QML APIは、そのような処理には直接対応していません。6.8では、QTextDocument::metaInformation(FrontMatter)を追加しています。(フロントマターの解析は別問題です。例えばyaml-cppを使用できます。 YAML 以外のフォーマットも使用されています。)

ファイル変換も可能です。既存の textFormatプロパティでは、フォーマット間の変換や、WYSIWYG と生のマークアップの切り替えも可能になりました。

テキストフォーマットAPI

C++では、テキストの変更範囲を定義し、その範囲にスタイルを適用するために、QTextCursorが必要になることがよくあります。これに相当するQMLの型はTextSelectionで、現時点では、alignment(整列)、color(色)、font(フォント)、text(テキスト)のプロパティを提供しています。TextEdit.cursorSelectionは、ユーザーが選択したテキストに対応するため、ブロックや文字のフォーマットプロパティを表示および変更するコントロールへのバインディングが可能です。詳細は、テキストエディタのサンプルを参照してください。

大きなドキュメント

以前の新機能ページで指摘したように、最近の数バージョンの Qt では、ビューポート外にあるテキストの大部分に対して、TextTextEditがシーングラフノードの生成を回避するようになりました。 これは、実際には、Item がその子(の一部)のビューポートとして機能するための一般的なメカニズムに基づいています。ここで紹介します。親(Flickableなど)がItemIsViewportフラグを設定し、子(TextEditなど)がItemObservesViewportフラグを設定します。 子クラスの clipRect() はビューポート内で表示される領域を提供します。テキストがこの最適化を必要とするほど「大きい」と判断された場合、TextEdit clipRect と重なるテキストブロックのみを scenegraph ノードに追加します。そのトレードオフとして、スクロール中は updatePaintNode()がより頻繁に呼び出され、その都度、どのブロックをビューポートに表示すべきかを判断する必要があります。

以下の画面録画では、内側の長方形がビューポートのサイズを示しており、段落がその領域外にスクロールされた際にどのようにして非表示になるかを確認できます。Markdownファイルには、長文で知られる書籍のテキスト全体が含まれています。しかし、すべてのブロックのシーングラフノードを一度に作成していたQtの旧バージョンよりも、メモリ使用量は適切です。

要約

まとめると、現在では、純粋なQMLでWYSIWYGリッチテキストエディタを記述することができます。弊社が提供するテキストエディタのサンプルでは、基本的なmain()関数以外のC++は必要なくなりました。