+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::updateLayout() {
+ switch(data(ChatLineModel::ColumnTypeRole).toUInt()) {
+ case ChatLineModel::TimestampColumn:
+ if(!haveLayout()) _layout = createLayout(QTextOption::WrapAnywhere, Qt::AlignLeft);
+ // fallthrough
+ case ChatLineModel::SenderColumn:
+ if(!haveLayout()) _layout = 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()) _layout = 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::clearLayout() {
+ delete _layout;
+ _layout = 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();
+ } // TODO: add selection format here
+ 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();
+ }
+ formats.append(selectFmt);
+ }
+ _layout->draw(painter, QPointF(0,0), formats, boundingRect());
+}
+
+qint16 ChatItem::posToCursor(const QPointF &pos) {
+ if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length();
+ if(pos.y() < 0) return 0;
+ if(!haveLayout()) updateLayout();
+ for(int l = _layout->lineCount() - 1; l >= 0; l--) {
+ QTextLine line = _layout->lineAt(l);
+ if(pos.y() >= line.y()) {
+ return line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
+ }
+ }
+ return 0;
+}
+
+void ChatItem::setFullSelection() {
+ _selectionMode = FullSelection;
+ update();
+}
+
+void ChatItem::clearSelection() {
+ _selectionMode = NoSelection;
+ update();
+}
+
+void ChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
+ if(event->buttons() & Qt::LeftButton) {
+ chatScene()->setSelectingItem(this); // removes earlier selection if exists
+ _selectionStart = _selectionEnd = posToCursor(event->pos());
+ _selectionMode = PartialSelection;
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+
+void ChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
+ if(contains(event->pos())) {
+ _selectionEnd = posToCursor(event->pos());
+ update();
+ } else {
+ setFullSelection();
+ ungrabMouse();
+ chatScene()->startGlobalSelection(this);
+ }
+}
+
+void ChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
+ if(_selectionMode != NoSelection) {
+ _selectionEnd = posToCursor(event->pos());
+ QString selection
+ = data(MessageModel::DisplayRole).toString().mid(qMin(_selectionStart, _selectionEnd), qAbs(_selectionStart - _selectionEnd));
+ QApplication::clipboard()->setText(selection, QClipboard::Clipboard); // TODO configure where selections should go
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+
+/*************************************************************************************************/
+