+ return fontMetrics()->lineSpacing(); // only contents can be multi-line
+
+ _lines = 1;
+ WrapColumnFinder finder(this);
+ while(finder.nextWrapColumn() > 0) _lines++;
+ return _lines * fontMetrics()->lineSpacing();
+}
+
+QTextLayout *ChatItem::createLayout(QTextOption::WrapMode wrapMode, Qt::Alignment alignment) {
+ QTextLayout *layout = new QTextLayout(data(MessageModel::DisplayRole).toString());
+
+ QTextOption option;
+ option.setWrapMode(wrapMode);
+ option.setAlignment(alignment);
+ layout->setTextOption(option);
+
+ QList<QTextLayout::FormatRange> formatRanges
+ = QtUi::style()->toTextLayoutList(data(MessageModel::FormatRole).value<UiStyle::FormatList>(), layout->text().length());
+ layout->setAdditionalFormats(formatRanges);
+ return layout;
+}
+
+void ChatItem::setLayout(QTextLayout *layout) {
+ if(!_layoutData)
+ _layoutData = new LayoutData;
+ _layoutData->layout = layout;
+}
+
+void ChatItem::updateLayout() {
+ switch(data(ChatLineModel::ColumnTypeRole).toUInt()) {
+ case ChatLineModel::TimestampColumn:
+ if(!haveLayout()) setLayout(createLayout(QTextOption::WrapAnywhere, Qt::AlignLeft));
+ // fallthrough
+ case ChatLineModel::SenderColumn:
+ if(!haveLayout()) setLayout(createLayout(QTextOption::WrapAnywhere, Qt::AlignRight));
+ layout()->beginLayout();
+ {
+ QTextLine line = layout()->createLine();
+ if(line.isValid()) {
+ line.setLineWidth(width());
+ line.setPosition(QPointF(0,0));
+ }
+ layout()->endLayout();
+ }
+ break;
+ case ChatLineModel::ContentsColumn: {
+ if(!haveLayout()) setLayout(createLayout(QTextOption::WrapAnywhere));
+
+ // Now layout
+ ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value<ChatLineModel::WrapList>();
+ if(!wrapList.count()) return; // empty chatitem
+
+ qreal h = 0;
+ WrapColumnFinder finder(this);
+ layout()->beginLayout();
+ forever {
+ QTextLine line = layout()->createLine();
+ if(!line.isValid())
+ break;
+
+ int col = finder.nextWrapColumn();
+ line.setNumColumns(col >= 0 ? col - line.textStart() : layout()->text().length());
+ line.setPosition(QPointF(0, h));
+ h += line.height() + fontMetrics()->leading();
+ }
+ layout()->endLayout();
+ }
+ break;
+ }
+}
+
+void ChatItem::clearLayoutData() {
+ delete _layoutData;
+ _layoutData = 0;
+}
+
+// NOTE: This is not the most time-efficient implementation, but it saves space by not caching unnecessary data
+// This is a deliberate trade-off. (-> selectFmt creation, data() call)
+void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+ Q_UNUSED(option); Q_UNUSED(widget);
+ if(!haveLayout()) updateLayout();
+ painter->setClipRect(boundingRect()); // no idea why QGraphicsItem clipping won't work
+ //if(_selectionMode == FullSelection) {
+ //painter->save();
+ //painter->fillRect(boundingRect(), QApplication::palette().brush(QPalette::Highlight));
+ //painter->restore();
+ //}
+ QVector<QTextLayout::FormatRange> formats;
+ if(_selectionMode != NoSelection) {
+ QTextLayout::FormatRange selectFmt;
+ selectFmt.format.setForeground(QApplication::palette().brush(QPalette::HighlightedText));
+ selectFmt.format.setBackground(QApplication::palette().brush(QPalette::Highlight));
+ if(_selectionMode == PartialSelection) {
+ selectFmt.start = qMin(_selectionStart, _selectionEnd);
+ selectFmt.length = qAbs(_selectionStart - _selectionEnd);
+ } else { // FullSelection
+ selectFmt.start = 0;
+ selectFmt.length = data(MessageModel::DisplayRole).toString().length();