QTableView使用QTextDocument绘制单行Html的文本缩略支持
如果要在QTableView的单元格支持Html,通常做法是使用QTextDocument绘制,由于带有标签的Html文本总是长于实际显示的文本,所以无法直接使用QFontMetrics::elidedText。如果对纯文本进行缩略,又不好判断对应Html是哪部分被替换了。
实际上QTextDocument支持文本编辑,Html文本会被转化为Qt的格式,因此,使用坐标去测试文本是否超出显示区域再进行替换,可实现不同的缩略模式。
方案原理
目前仅简单实现了单行文本的缩略,如果要实现多行文本的或者带有图片等元素,就比较麻烦,这里不做讨论。
文档布局
QTextDocument::documentLayout接口可以获取到文档的布局,QAbstractTextDocumentLayout提供了hitTest接口测试指定坐标的文档位置:
virtual int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy);
其中第二个参数是指精确模式,经过测试:
- Qt::ExactHit返回坐标位置的文档位置,如果不在文字区域则返回-1
- Qt::FuzzyHit返回坐标位置最近的文档位置,如果不在文字区域可能返回文档尾部。与Qt::ExactHit区别是,当坐标在某个字符区域偏左,返回字符位置,偏右则返回下一位置,类似编辑器里用将光标移动至鼠标点击位置的策略。
文档编辑
QTextDocument使用QTextCursor进行编辑,QTextCursor提供了非常多的编辑操作。这里主要用来选择需要省略的文本并替换为“…”。
实现
- 首先创建好QTextDocument
这里强制设置为不换行、设置字体(个人使用QWidget进行测试)QTextDocument doc; QTextOption option = doc.defaultTextOption(); option.setWrapMode(QTextOption::NoWrap); doc.setDefaultTextOption(option); doc.setDefaultFont(this->font()); doc.setHtml("Test html text, <a href=\"http://www.baiu.com\">nchor text</a>, single line");
- 测试是否超出文本区域
rect指文本区域,如果右侧的坐标是否超出文本,由于精确测试,所以每次使用字体高度的一半位置。position 如果大于等于0,表示需要缩略。
这里应该也可以使用文档宽度判断。QFontMetrics fm = this->fontMetrics(); QPoint testPoint(rect.right(), fm.height() / 2); QAbstractTextDocumentLayout * textlayout = doc.documentLayout(); int position = textlayout->hitTest(testPoint, Qt::ExactHit);
- 测试替换缩略位置并替换
Qt支持左、右、居中缩略模式:
居中缩略比较麻烦,假设文字区域中间要替换为 “…”,所以先计算左侧,再加上 “…” 宽度计算右侧,这里为了减少误差,使用了两种模式测试。int dotWidth = fm.width("..."); switch (elidMode) { case Qt::ElideRight: { // 右侧文本区域减去...宽度 qreal cutPos = textlayout->hitTest(QPointF(rect.width() - dotWidth, fm.height() / 2), Qt::ExactHit); if(cutPos >= 0) { QTextCursor cursor(&doc); cursor.setPosition(cutPos); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.insertText("...", cursor.block().charFormat()); } } break; case Qt::ElideLeft: { // 由于要替换,超出宽度+...宽度 qreal testX = doc.size().width() - rect.width() + dotWidth; int cutPos = textlayout->hitTest(QPointF(testX, fm.height() / 2), Qt::ExactHit); if(cutPos >= 0) { QTextCursor cursor(&doc); cursor.setPosition(0); // 坐标位置为字符位置,所以需要 +1 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, cutPos + 1); cursor.insertText("...", cursor.block().charFormat()); } } break; case Qt::ElideMiddle: { qreal testX = (rect.width() - dotWidth)/ 2.0; int cutLeft = textlayout->hitTest(QPoint(testX, fm.height() / 2), Qt::FuzzyHit); if(cutLeft >= 0) { testX += (doc.size().width() - rect.width()) + dotWidth; int cutRight = textlayout->hitTest(QPoint(testX, fm.height() / 2), Qt::ExactHit); if(cutRight >= 0) { // 减少缩略的偏移 int prefer = textlayout->hitTest(QPoint(testX, fm.height() / 2), Qt::FuzzyHit); cutRight = cutRight == prefer ? (cutRight + 1): prefer; QTextCursor cursor(&doc); cursor.setPosition(cutLeft); cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, cutRight - cutLeft); cursor.insertText("...", cursor.block().charFormat()); } } } default: break; }
三种模式使用QTextCursor替换文本时,第二个参数都制定了新的QTextCharFormat,与链接颜色区分开。
效果
以下是三种不同的省略模式的表现。
居中模式省略的时候“…”可能出现在超链接中间,所以替换时应该判断一下替换区域从左到右是否处于连续的同一链接,是的话应该使用链接样式。这里先保留这个错误。
交互与样式
QTextDocument不支持直接交互,所以如果要实现类似点击链接跳转的行为,就需要通过接口获取链接。
QTextDocument设置文本后就已经跟根据文本创建了所有格式,如果要替换一些默认样式,比如超链接,就需要查找链接进行调整了。
加上缩略模式的交互,就需要根据缩略后的内容判断,有时间再考虑怎么做吧。