Qt 有提供一個內建 QTextEdit
元件(官方文件),可以用來顯示、編輯文件;而理論上,他也是有支援比較豐富的文字格式的;他基本上可以處理絕大部分的字型、排版等格式設定,同時也可以支援圖片的顯示。
不過由於他內部有自己的資源管理系統,再加上預設的設定問題,所以如果直接拿 QTextEdit
來用的話,其實「貼上圖片」這個動作應該算是不可用的。
根據官方文件的說法(頁面),他預設可以插入純文字、HTML、含有格式的文字;而如果要處理其他形式的資料,則會需要繼承 QTextEdit
、然後重新實作 canInsertFromMimeData()
與 insertFromMimeData()
這兩個函式,並針對 MIME 格式來做判斷。
在《Several ways of placing an image in a QTextEdit》這邊,有人有提供一個簡單的範例;不過這個例子並不能處理從 Word 複製來的資料,所以這邊又做了些修改如下:
class QImageTextEdit : public QTextEdit { public: QImageTextEdit(QWidget* pParent = nullptr) : QTextEdit(pParent) {} bool canInsertFromMimeData(const QMimeData* source) const { return source->hasImage() || source->hasUrls() || QTextEdit::canInsertFromMimeData(source); } void insertFromMimeData(const QMimeData* source) { if (source->hasImage()) { QImage img = qvariant_cast<QImage>(source->imageData()); if (!img.isNull()) { QUrl url(QString("dropped_image_%1").arg(++m_uCounter)); document()->addResource(QTextDocument::ImageResource, url, img); textCursor().insertImage(url.toString()); } } else if (source->hasUrls()) { foreach(QUrl url, source->urls()) { QString sName = url.toLocalFile(); QImage img(sName); if (!img.isNull()) { document()->addResource(QTextDocument::ImageResource, sName, img); textCursor().insertImage(sName); } } } else if (source->hasHtml()) { QString sHtml = source->html().replace(R"(src="file://)", R"(src=")"); textCursor().insertHtml(sHtml); } else { QTextEdit::insertFromMimeData(source); } } protected: std::size_t m_uCounter = 0; };
首先,這邊的 canInsertFromMimeData()
主要是告知 Qt 系統,這個文字編輯器的元件可以支援包含圖片、或是包含網址的 MIME 資料,這樣在貼上這類型資料的時候,Qt 才會讓這個元件去處理。
在 insertFromMimeData()
裡面,則是真的去處理要貼上的 MIME 資料;這裡面分成三個部分、代表不同的來源;理論上這樣的寫法可以在一定的程度上處理貼上圖片、貼上圖片檔案、貼上包含圖片的 HTML 的情境(Word 複製的會是這種)。
內部的 QTextDocument 與影像資源
不過在開始講各別的內容之前,這邊要先提一下 QTextEdit
內部對於圖片到底是怎麼管理、使用的。
QTextEdit
的內部是使用 QTextDocument
(文件)來儲存整個要顯示的文件,而在文件內要顯示的圖檔,則是以「資源」(resource)的形式來做額外的管理、儲存;如果要在裡面加入一張圖片,基本上需要:
- 透過
addResource()
這個函式、將QImage
以QTextDocument::ImageResource
的形式加入QTextDocument
內部的資源管理系統,之後則是要透過資源的名稱(URL 的形式)來存取。 - 在加入資源後,就可以在內文中加入對應圖片的資源名稱(URL)、來顯示圖片。
下面就是一個簡單的例子:
QTextEdit* editor = new QImageTextEdit(); QImage image(R"(d:\image.jpg)"); editor->document()->addResource( QTextDocument::ImageResource, QUrl("mydata://image.jpg"), image); editor->append("<img src=\"mydata://image.jpg\" />");
這邊就是先讀取 d:\image.jpg
這個檔案,然後以「mydata://image.jpg
」這個名稱加入資源管理系統。
之後則是透過 QTextEdit
的 append()
函式、以 HTML 的 <img>
標籤的形式附加在文件的最後。實際上,可以插入圖片名稱的方法很多種,這邊只是其中一種而已。
而如果有需要,則也可以透過 QTextDocument
的 resource()
這個函式來取得需要的影像資源。
針對圖片與檔案的處理
在對 QTextEdit
處理影像的方法有了基本的認知後,再回來看 insertFromMimeData()
的處理方法。
首先,第一段當 source->hasImage()
為真的時候,代表是從剪貼簿貼上純粹的圖片資料。這時候,可以透過 QMimeData
(官方文件)的 imageData()
這個函式,來取得影像資料;不過由於他回傳的形式是 QVariant
、所以還需要自己轉型成 QImage
來用。
而在確認圖片有正確讀取後,則就可以透過 addResource()
這個函式來把剛讀進來的檔案加入 QTextDocument
的資源管理系統了~不過由於沒有對應可以當名稱的資訊,所以這邊是建立一個「dropped_image_0
」這種類型的序列號字串、來做為名稱、以避免衝突。
之後,則是透過 textCursor()
這個函式、來取得對應目前文字輸入游標的位置的操作物件(文件)、然後透過他的 insertImage()
來在目前的位置插入這個圖片。
而如果是拖曳檔案到 QTextEdit
的時候,QMimeData
實際上會是包含一個或多個代表檔案的 URL,所以這時候 source->hasUrls()
會回傳 true,然後就需要針對 urls()
回傳的 URL 陣列個別作處理。
這邊個別處理的方法基本上和上面貼上圖片的時候一樣,唯一不同的,就是資源的名稱是直接拿檔案名稱來用了。
處理 HTML
上面兩個處理方法基本上都是 Stack Overflow 那篇文章提供的方案,這樣寫確實可以對應貼上單獨的圖片、或是從檔案總管拉圖片進來。
但是如果是想把 Word 這類的工具編輯好、含圖片的內容複製進來的話,那似乎就不能用了。
Heresy 試著從 Word 貼一段含圖片的內容道測試程式上,文字的部分算正常,圖片看來也有試著要處理、但是卻會因為讀不到圖片、而變成一個文件的 ICON…
稍微研究了一下,發現在要貼上從 Word 複製的內容的時候,雖然還是會經過 insertFromMimeData()
,但是 QMimeData
的 hasImage()
和 hasUrls()
都會回傳 false。如果是以《Several ways of placing an image in a QTextEdit》提供的版本的話,會變成是直接用 insertFromMimeData()
來插入內容。
而雖然看來內容有正確地插入、卻沒有辦法顯示圖片是什麼原因呢?測試了一下後,發現應該是這樣貼上來的資料會被視為是 HTML、而裡面的圖檔的標籤會是類似下面的形式:
<img width=196 height=209 src=\"file://C:/Users/Heresy/AppData/Local/Temp/msohtmlclip1/01/clip_image002.png\" alt=\"... 自動產生的描述\" v:shapes=\"圖片_x0020_1\">
這邊可以看到,他會將圖檔儲存在使用者的暫存資料夾裡面拿來使用。
如 Qt 這邊沒辦法正確讀取的原因,測試了一下後發現似乎是因為路徑前面多了「file://
」、所以不是本地路徑的關係?
所以這邊臨時性的處理方法就很簡單了~就是上面的程式碼的第三段,也就是:
else if (source->hasHtml()) { QString sHtml = source->html().replace(R"(src="file://)", R"(src=")"); textCursor().insertHtml(sHtml); }
這邊基本上就是當來源是 HTML 類型的資料的時候,先透過 html()
這個函式來取得 HTML 格式的原始內容,然後很粗暴地、透過 QString
的 replace()
把所有的「src="file://
」取代成「src="
」,讓他的圖檔 URL 變成 local 端的路徑了!
之後,再透過 QTextCursor
的 inseertHtml()
把整個 HTML 插入到當下游標的位置,就可以了~這個狀況下,他會自己去分析 HTML、並且試著將裡面使用的圖檔讀取到資源管理器裡面、不需要手動處理。
透過這個方法,基本上算是勉強可以符合個人的需求,不過有的地方還是得繼續修改、追加。
比如說,目前這樣的寫法只是讓他可以當下在 QTextEdit
中可以顯示、並繼續編輯而已;如果想要儲存下來的話,雖然可以透過 toHtml()
或 toMarkdown()
來輸出,但是真的要實用的話,有用到的圖片應該都還是得另外再處理、否則之後要讀取的時候應該會讀不到的(尤其是直接貼上的部分)。
而如果是要可以讀檔、繼續修改的話,採用計數器的形式來產生圖片的 URL 的形式其實也不算好了,如果還有需要讀取檔案的話,就有可能造成衝突;這部分應該還是得檢查是否有重複、或是改用 hash 之類的方法可能還比較合適?
這部分,就之後有機會再整理了。