+void ChatItem::setFullSelection() {
+ if(_selectionMode != FullSelection) {
+ _selectionMode = FullSelection;
+ update();
+ }
+}
+
+void ChatItem::clearSelection() {
+ if(_selectionMode != NoSelection) {
+ _selectionMode = NoSelection;
+ update();
+ }
+}
+
+void ChatItem::continueSelecting(const QPointF &pos) {
+ _selectionMode = PartialSelection;
+ _selectionEnd = posToCursor(pos);
+ update();
+}
+
+bool ChatItem::isPosOverSelection(const QPointF &pos) const {
+ if(_selectionMode == FullSelection)
+ return true;
+ if(_selectionMode == PartialSelection) {
+ int cursor = posToCursor(pos);
+ return cursor >= qMin(_selectionStart, _selectionEnd) && cursor <= qMax(_selectionStart, _selectionEnd);
+ }
+ return false;
+}
+
+QList<QRectF> ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity caseSensitive) {
+ QList<QRectF> resultList;
+ const QAbstractItemModel *model_ = model();
+ if(!model_)
+ return resultList;
+
+ QString plainText = model_->data(model_->index(row(), column()), MessageModel::DisplayRole).toString();
+ QList<int> indexList;
+ int searchIdx = plainText.indexOf(searchWord, 0, caseSensitive);
+ while(searchIdx != -1) {
+ indexList << searchIdx;
+ searchIdx = plainText.indexOf(searchWord, searchIdx + 1, caseSensitive);
+ }
+
+ QTextLayout layout;
+ initLayout(&layout);
+ foreach(int idx, indexList) {
+ QTextLine line = layout.lineForTextPosition(idx);
+ qreal x = line.cursorToX(idx);
+ qreal width = line.cursorToX(idx + searchWord.count()) - x;
+ qreal height = line.height();
+ qreal y = height * line.lineNumber();
+ resultList << QRectF(x, y, width, height);
+ }
+
+ return resultList;
+}
+
+void ChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clickMode) {
+ // single clicks are already handled by the scene (for clearing the selection)
+ if(clickMode == ChatScene::DragStartClick) {
+ chatScene()->setSelectingItem(this);
+ _selectionStart = _selectionEnd = posToCursor(pos);
+ _selectionMode = NoSelection; // will be set to PartialSelection by mouseMoveEvent
+ update();
+ }
+}
+
+void ChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
+ if(event->buttons() == Qt::LeftButton) {
+ if(contains(event->pos())) {
+ qint16 end = posToCursor(event->pos());
+ if(end != _selectionEnd) {
+ _selectionEnd = end;
+ _selectionMode = (_selectionStart != _selectionEnd ? PartialSelection : NoSelection);
+ update();
+ }
+ } else {
+ setFullSelection();
+ chatScene()->startGlobalSelection(this, event->pos());
+ }
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+
+void ChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
+ if(event->buttons() == Qt::LeftButton)
+ event->accept();
+ else
+ event->ignore();
+}
+
+void ChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
+ if(_selectionMode != NoSelection && event->button() == Qt::LeftButton) {
+ chatScene()->selectionToClipboard(QClipboard::Selection);
+ event->accept();
+ } else
+ event->ignore();
+}
+
+void ChatItem::addActionsToMenu(QMenu *menu, const QPointF &pos) {
+ Q_UNUSED(pos);
+
+ GraphicalUi::contextMenuActionProvider()->addActions(menu, chatScene()->filter(), data(MessageModel::BufferIdRole).value<BufferId>());
+}
+
+// ************************************************************
+// SenderChatItem
+// ************************************************************
+
+void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+ Q_UNUSED(option); Q_UNUSED(widget);
+ paintBackground(painter);
+
+ QTextLayout layout;
+ initLayout(&layout);
+ qreal layoutWidth = layout.minimumWidth();
+ qreal offset = 0;
+ if(chatScene()->senderCutoffMode() == ChatScene::CutoffLeft)
+ offset = qMin(width() - layoutWidth, (qreal)0);
+ else
+ offset = qMax(layoutWidth - width(), (qreal)0);
+
+ if(layoutWidth > width()) {
+ // Draw a nice gradient for longer items
+ // Qt's text drawing with a gradient brush sucks, so we use an alpha-channeled pixmap instead
+ QPixmap pixmap(layout.boundingRect().toRect().size());
+ pixmap.fill(Qt::transparent);
+ QPainter pixPainter(&pixmap);
+ layout.draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), additionalFormats());
+ pixPainter.end();
+
+ // Create alpha channel mask
+ QPixmap mask(pixmap.size());
+ QPainter maskPainter(&mask);
+ QLinearGradient gradient;
+ if(offset < 0) {
+ gradient.setStart(0, 0);
+ gradient.setFinalStop(12, 0);
+ gradient.setColorAt(0, Qt::black);
+ gradient.setColorAt(1, Qt::white);
+ } else {
+ gradient.setStart(width()-10, 0);
+ gradient.setFinalStop(width(), 0);
+ gradient.setColorAt(0, Qt::white);
+ gradient.setColorAt(1, Qt::black);
+ }
+ maskPainter.fillRect(boundingRect(), gradient);
+ pixmap.setAlphaChannel(mask);
+ painter->drawPixmap(0, 0, pixmap);
+ } else {
+ layout.draw(painter, QPointF(0,0), additionalFormats(), boundingRect());
+ }
+}
+
+// ************************************************************
+// ContentsChatItem
+// ************************************************************
+
+ContentsChatItem::ActionProxy ContentsChatItem::_actionProxy;
+
+ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent)
+ : ChatItem(0, 0, pos, parent),
+ _data(0)
+{
+ setGeometryByWidth(width);
+}
+
+QFontMetricsF *ContentsChatItem::fontMetrics() const {
+ return QtUi::style()->fontMetrics(data(ChatLineModel::FormatRole).value<UiStyle::FormatList>().at(0).second, 0);
+}
+
+ContentsChatItem::~ContentsChatItem() {
+ delete _data;
+}
+
+ContentsChatItemPrivate *ContentsChatItem::privateData() const {
+ if(!_data) {
+ ContentsChatItem *that = const_cast<ContentsChatItem *>(this);
+ that->_data = new ContentsChatItemPrivate(findClickables(), that);
+ }
+ return _data;
+}
+
+qreal ContentsChatItem::setGeometryByWidth(qreal w) {
+ // We use this for reloading layout info as well, so we can't bail out if the width doesn't change
+
+ // compute height
+ int lines = 1;
+ WrapColumnFinder finder(this);
+ while(finder.nextWrapColumn(w) > 0)
+ lines++;
+ qreal h = lines * fontMetrics()->lineSpacing();
+ delete _data;
+ _data = 0;
+
+ if(w != width() || h != height()) {
+ prepareGeometryChange();
+ setGeometry(w, h);
+ }
+ return h;
+}
+
+void ContentsChatItem::doLayout(QTextLayout *layout) const {