+// ************************************************************
+// ContentsChatItem
+// ************************************************************
+
+ContentsChatItem::ActionProxy ContentsChatItem::_actionProxy;
+
+ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent)
+ : ChatItem(0, 0, pos, parent),
+ _data(0)
+{
+ const QAbstractItemModel *model_ = model();
+ QModelIndex index = model_->index(row(), column());
+ _fontMetrics = QtUi::style()->fontMetrics(model_->data(index, ChatLineModel::FormatRole).value<UiStyle::FormatList>().at(0).second);
+
+ setGeometryByWidth(width);
+}
+
+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) {
+ if(w != width()) {
+ prepareGeometryChange();
+ setWidth(w);
+ // compute height
+ int lines = 1;
+ WrapColumnFinder finder(this);
+ while(finder.nextWrapColumn() > 0)
+ lines++;
+ setHeight(lines * fontMetrics()->lineSpacing());
+ delete _data;
+ _data = 0;
+ }
+ return height();
+}
+
+void ContentsChatItem::doLayout(QTextLayout *layout) const {
+ 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 += fontMetrics()->lineSpacing();
+ }
+ layout->endLayout();
+}
+
+// NOTE: This method is not threadsafe and not reentrant!
+// (RegExps are not constant while matching, and they are static here for efficiency)
+QList<ContentsChatItem::Clickable> ContentsChatItem::findClickables() const {
+ // For matching URLs
+ static QString urlEnd("(?:>|[,.;:\"]*\\s|\\b|$)");
+ static QString urlChars("(?:[,.;:]*[\\w\\-~@/?&=+$()!%#*|{}\\[\\]])");
+
+ static QRegExp regExp[] = {
+ // URL
+ // QRegExp(QString("((?:https?://|s?ftp://|irc://|mailto:|www\\.)%1+|%1+\\.[a-z]{2,4}(?:?=/%1+|\\b))%2").arg(urlChars, urlEnd)),
+ QRegExp(QString("((?:(?:https?://|s?ftp://|irc://|gopher://|mailto:)|www)%1+)%2").arg(urlChars, urlEnd), Qt::CaseInsensitive),
+
+ // Channel name
+ // We don't match for channel names starting with + or &, because that gives us a lot of false positives.
+ QRegExp("((?:#|![A-Z0-9]{5})[^,:\\s]+(?::[^,:\\s]+)?)\\b", Qt::CaseInsensitive)
+
+ // TODO: Nicks, we'll need a filtering for only matching known nicknames further down if we do this
+ };
+
+ static const int regExpCount = 2; // number of regexps in the array above
+
+ qint16 matches[] = { 0, 0, 0 };
+ qint16 matchEnd[] = { 0, 0, 0 };