這篇算是延續前面一篇 QTextEdit
處理圖片,來進一步看一下怎麼去處理 QTextDocument
(官方文件)的內容。不過老實說,這邊 Heresy 沒有研究得很徹底、搞不好也有錯;如果有發現有錯的話,也麻煩提點一下了。
首先,他主要的結構應該是由 QTextFrame
(文件)和 QTextBlock
(文件)組成的樹狀結構,每一個文件有一個 root frame、底下透過 block 和 frame 來組成整份文件;而 frame 底下則可以包含多個 block、但是 block 也可以直接在 root frame 下。
而實際上如果想要走過整個文件,以 QTextDocument
的 API 來看,應該是要透過 begin()
/ end()
、以 QTextBlock
為單元來掃過整個文件,右圖就是官方提供的示意圖。
這邊也可以看到,在這個流程中、基本上完全不會碰到 QTextFrame
的資訊。這也讓 Heresy 有點不太理解 QTextFrame
的存在意義?
老實說,個人覺得 QTextDocument
的設計還滿…詭異的?
而實際上,QTextBlock
也還不是整個文件的最小單位。他代表的意義大致上「段落」(paragraph)、本身是也是由多個 QTextFragment
(文件)組成的。
所以如果這邊先無視 frame 這一層的話,整個 QTextDocument
的組成是下面這樣的三層式架構:
- document
- block
- fragment
- block
掃過整份文件
總之,如果單純要掃過整個文件的話,最簡單的方法就是直接透過 QTextDocument
的 begin()
/ end()
來做,他的寫法大致上會是:
for (QTextBlock it = pDoc->begin(); it != pDoc->end(); it = it.next()) qDebug() << it.text();
這邊的 pDoc
就是 QTextDocument
的指標。
而這樣的寫法基本上就可以簡單地把整份文件的「文字部分」依 block 一個一個輸出了。
如果要再更細緻的話,則是可以寫成:
size_t i = 0; QTextDocument* pDoc = edit->document(); for (QTextBlock block = pDoc->begin(); block != pDoc->end(); block = block.next()) { qDebug() << "Block #" << ++i; for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it) { QTextFragment fragment = it.fragment(); if (fragment.isValid()) qDebug() << " > " << fragment.text(); } }
這樣的話就可以針對每個 fragment 來做拆分了~
至於一個 block 什麼時候會拆分成多個 fragment 呢?主要應該就是格式有變化(字體、顏色等等)的時候,就會拆成不同的 fragment;所以如果整個 block 都是同一個格式的話,那就只會有一個 fragment。
不過,個人覺得他這邊設計比較討厭的,是雖然 QTextDocument
和 QTextBlock
都是提供 begin()
/ end()
來操作,但是實際上兩者的操作邏輯、方法不完全一樣、沒有統一;同時,他們也都不符合 STL 的 iterator 的設計、所以無法使用標準的方法來操作。
再來,就是整個 QTextBlock
本身就是設計成唯獨的,所以上面整個流程都只能讀取、不能拿來修改資料… orz
格式(format)
先不管不能修改的問題,前面的例子基本上都只有處理文字的部分而已;而其他東西,主要則是格式。
在 QTextDocument
的設計裡面,有定義 QTextFormat
(文件)、以及衍生出來的 QTextCharFormat
、QTextBlockFormat
、QTextListFormat
、QTextTableFormat
等類別、用來紀錄文字以外的資訊。
以 QTextBlock
來說,會有對應段落格式的 QTextBlockFormat
、以及對應文字格式的 QTextCharFormat
可以使用;而 QTextFragment
的話,則只有 QTextCharFormat
。
不過以圖片來說,實際上相關的資訊是透過 QTextImageFormat
(文件)這個繼承自 QTextCharFormat
的類別來儲存的,所以實際上要找到文件中的圖片的話,其實是要透過格式來找的!
像是透過下面的程式,就可以找到文件中所有的圖片了~
for (QTextBlock block = pDoc->begin(); block != pDoc->end(); block = block.next()) { for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it) { QTextFragment fragment = it.fragment(); QTextCharFormat charFmt = fragment.charFormat(); if (charFmt.isImageFormat()) { QTextImageFormat imageFmt = charFmt.toImageFormat(); qDebug() << imageFmt.name(); } } }
而 QTextImageFormat
的 name()
這個函式代表的是這張圖片的資源名稱。
至於其他的格式,也是可以用類似的方法來找、或是讀取對應的資料。只是因為這種寫法對於文件來說是唯獨的,所以雖然大多也都可以做修改、但是沒有辦法對原始文件造成有效的修改就是了。
使用 QTextCursor 修改文件
如果要透過程式修改 QTextDocument
的內容的話,基本上是要透過 QTextCursor
(文件)這個對應到文字編輯器中的輸入游標的類別來進行的。
他基本上就像編輯器中的輸入游標一樣,會存在於文件中的某個位置,在程式中可以透過 position()
這個函式來取得。
此外,他也還有另一個「anchor」的位置,用來處理文字的選取。當 anchor()
回傳的值和 position()
相同的時候,就代表沒有選取東西,不同的時候,則就有一個選取範圍。
而透過 QTextCursor
的提供的各種功能,就可以拿來調整文件的格式、刪除文件中的特定內容、插入新的內容了~同時,這樣的編輯方法也可以提供「undo」的功能,讓整個使用體驗更好。
在使用的時候,基本上就是先去將 QTextCursor
的位置或是選取區設定到想要的位置、然後再透過各種編輯的函式來針對文章內容作處理了~
比如說下面的程式碼就會在文件的一開始,加入一行「Hello World」:
QTextCursor cursor(pDoc); cursor.insertText("Hello World\n");
如果想要插入到文章的最後,也可以先調整位置後再執行插入就可以了:
QTextCursor cursor(pDoc); cursor.movePosition(QTextCursor::End); cursor.insertText("Hello World\n");
他的 movePosition()
主要是透過 QTextCursor::MoveOperation
、針對文件的結構來移動,在使用上應該也算是相當地方變了~
比如說想要選取整個文件的第一個文字的話,就可以寫成:
QTextCursor cursor(pDoc); cursor.insertText(“Hello World\n“); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
cursor.insertHtml(“<font color=red>Hello</font>“);
這邊先透過 movePosition()
把游標移到文章的開頭,然後再把游標以「KeepAnchor
」 的狀態、移到目前文字的結尾,這樣就可以把第一個單字、也就是「Hello」選起來了~
而此時再透過 insertHtml()
插入一段用 HTML 來描述格式的文字後,就可以把本來的黑色「Hello」刪除、換成紅色的「Hello」了。
除了可以透過 movePosition()
來移動游標外,他也還有提供 setPosition()
這個函式,可以針對實際的位置(整數形式)來移動游標;在使用時可以針對需要來決定要用哪種方法,如果是要針對 fragment 或是 block 的 position()
回報的位置來做設定話,用 setPosition()
應該就會比較合適。
除了直接插入一般的文字外,也可以透過其他的 insertX()
來插入圖片、表格、清單、HTML 等等;同時,也可以插入 QTextDocument
定義的 frame、block、fragment。
此外,這邊也可以透過 setXFormat()
或 mergeXFormat()
這類的函式來調製、設定文件的格式。
比如說,如果想要把整份文件的圖片大小都縮小一半的話,可以寫成下面的樣子:
QTextDocument* pDoc = edit->document(); QTextCursor cursor(pDoc); for (QTextBlock block = pDoc->begin(); block != pDoc->end(); block = block.next()) { for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it) { QTextFragment fragment = it.fragment(); QTextCharFormat charFmt = fragment.charFormat(); if (charFmt.isImageFormat()) { QTextImageFormat imageFmt = charFmt.toImageFormat(); imageFmt.setWidth(imageFmt.width() / 2); imageFmt.setHeight(imageFmt.height() / 2); cursor.setPosition(fragment.position()); cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); cursor.setCharFormat(imageFmt); } } }
這篇大概就先這樣了。
實際上,QTextDocument
真的滿複雜、功能也滿多的,所以這邊大致上也就是簡單地把主要功能帶一下而已了~其他的東西還是得去看官方文件的。