- while(finder.nextWrapColumn() > 0)
- lines++;
- setHeight(lines * fontMetrics()->lineSpacing());
- }
- return height();
-}
-
-void ContentsChatItem::updateLayout() {
- if(!privateData()) {
- ContentsChatItemPrivate *data = new ContentsChatItemPrivate(createLayout(QTextOption::WrapAnywhere), findClickables(), this);
- setPrivateData(data);
- }
-
- // 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 += 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() {
- // For matching URLs
- static QString urlEnd("(?:>|[,.;:\"]*\\s|\\b|$)");
- static QString urlChars("(?:[\\w\\-~@/?&=+$()!%#]|[,.;:]\\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://|mailto:)|www)%1+)%2").arg(urlChars, urlEnd)),
-
- // 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")
-
- // 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 };
-
- QString str = data(ChatLineModel::DisplayRole).toString();
-
- QList<Clickable> result;
- qint16 idx = 0;
- qint16 minidx;
- int type = -1;
-
- do {
- type = -1;
- minidx = str.length();
- for(int i = 0; i < regExpCount; i++) {
- if(matches[i] < 0 || matchEnd[i] > str.length()) continue;
- if(idx >= matchEnd[i]) {
- matches[i] = str.indexOf(regExp[i], qMax(matchEnd[i], idx));
- if(matches[i] >= 0) matchEnd[i] = matches[i] + regExp[i].cap(1).length();
- }
- if(matches[i] >= 0 && matches[i] < minidx) {
- minidx = matches[i];
- type = i;
- }
- }
- if(type >= 0) {
- idx = matchEnd[type];
- if(type == Clickable::Url && str.at(idx-1) == ')') { // special case: closing paren only matches if we had an open one
- if(!str.mid(matches[type], matchEnd[type]-matches[type]).contains('(')) matchEnd[type]--;
- }
- result.append(Clickable((Clickable::Type)type, matches[type], matchEnd[type] - matches[type]));
- }
- } while(type >= 0);
-
- /* testing
- if(!result.isEmpty()) qDebug() << str;
- foreach(Clickable click, result) {
- qDebug() << str.mid(click.start, click.length);
- }
- */
- return result;
-}
-
-QVector<QTextLayout::FormatRange> ContentsChatItem::additionalFormats() const {
- // mark a clickable if hovered upon
- QVector<QTextLayout::FormatRange> fmt;
- if(privateData()->currentClickable.isValid()) {
- Clickable click = privateData()->currentClickable;
- QTextLayout::FormatRange f;
- f.start = click.start;
- f.length = click.length;
- f.format.setFontUnderline(true);
- fmt.append(f);
- }
- return fmt;
-}
-
-void ContentsChatItem::endHoverMode() {
- if(hasLayout() && privateData()->currentClickable.isValid()) {
- setCursor(Qt::ArrowCursor);
- privateData()->currentClickable = Clickable();
-#ifdef HAVE_WEBKIT
- privateData()->clearPreview();
-#endif
- update();
- }