- while(finder.nextWrapColumn() > 0)
- lines++;
- setHeight(lines * fontMetrics()->lineSpacing());
- }
- return height();
-}
-
-void ContentsChatItem::doLayout() {
- 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\\-~@/?&=+$()!%#]|[,.;:]\\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(hasPrivateData()) {
- if(privateData()->currentClickable.isValid()) {
- setCursor(Qt::ArrowCursor);
- privateData()->currentClickable = Clickable();
- }
- clearWebPreview();
- update();
- }
-}
-
-void ContentsChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
- privateData()->hasDragged = false;
- ChatItem::mousePressEvent(event);
-}
-
-void ContentsChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
- if(!event->buttons() && !privateData()->hasDragged) {
- // got a click
- Clickable click = privateData()->currentClickable;
- if(click.isValid()) {
- QString str = data(ChatLineModel::DisplayRole).toString().mid(click.start, click.length);
- switch(click.type) {
- case Clickable::Url:
- if(!str.contains("://"))
- str = "http://" + str;
- QDesktopServices::openUrl(str);
- break;
- case Clickable::Channel:
- // TODO join or whatever...
- break;
- default:
- break;
- }
- }
- }
- ChatItem::mouseReleaseEvent(event);
-}
-
-void ContentsChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
- // mouse move events always mean we're not hovering anymore...
- endHoverMode();
- // also, check if we have dragged the mouse
- if(hasPrivateData() && !privateData()->hasDragged && event->buttons() & Qt::LeftButton
- && (event->buttonDownScreenPos(Qt::LeftButton) - event->screenPos()).manhattanLength() >= QApplication::startDragDistance())
- privateData()->hasDragged = true;
- ChatItem::mouseMoveEvent(event);
-}
-
-void ContentsChatItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
- endHoverMode();
- event->accept();
-}
-
-void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
- bool onClickable = false;
- qint16 idx = posToCursor(event->pos());
- for(int i = 0; i < privateData()->clickables.count(); i++) {
- Clickable click = privateData()->clickables.at(i);
- if(idx >= click.start && idx < click.start + click.length) {
- if(click.type == Clickable::Url) {
- onClickable = true;
- showWebPreview(click);
- } else if(click.type == Clickable::Channel) {
- // TODO: don't make clickable if it's our own name
- //onClickable = true; //FIXME disabled for now
- }
- if(onClickable) {
- setCursor(Qt::PointingHandCursor);
- privateData()->currentClickable = click;
- update();
- break;
- }
- }
- }
- if(!onClickable) endHoverMode();
- event->accept();
-}
-
-void ContentsChatItem::showWebPreview(const Clickable &click) {
-#ifdef HAVE_WEBKIT
- QTextLine line = layout()->lineForTextPosition(click.start);
- qreal x = line.cursorToX(click.start);
- qreal width = line.cursorToX(click.start + click.length) - x;
- qreal height = line.height();
- qreal y = height * line.lineNumber();
-
- QPointF topLeft = scenePos() + QPointF(x, y);
- QRectF urlRect = QRectF(topLeft.x(), topLeft.y(), width, height);
-
- QString url = data(ChatLineModel::DisplayRole).toString().mid(click.start, click.length);
- if(!url.contains("://"))
- url = "http://" + url;
- chatScene()->loadWebPreview(this, url, urlRect);
-#endif