X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fqtui%2Fchatitem.cpp;h=819877aeb6d666fdbb0ac5d6134ff43e100ff21c;hp=86fb133205e42a230c0d0466b6cff072f6762fa5;hb=064dcac965d1e724a0434683685a24ec7e6ba855;hpb=755ae9469c82d0e3557a4fe2e05461ca0c270bb2 diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index 86fb1332..819877ae 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -58,6 +58,21 @@ QVariant ChatItem::data(int role) const { return model()->data(index, role); } +qint16 ChatItem::posToCursor(const QPointF &pos) const { + if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length(); + if(pos.y() < 0) return 0; + + QTextLayout layout; + initLayout(&layout); + 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::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapMode, Qt::Alignment alignment) const { Q_ASSERT(layout); @@ -69,32 +84,45 @@ void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapM layout->setTextOption(option); QList formatRanges - = QtUi::style()->toTextLayoutList(data(MessageModel::FormatRole).value(), layout->text().length()); + = QtUi::style()->toTextLayoutList(data(MessageModel::FormatRole).value(), layout->text().length(), data(ChatLineModel::MsgLabelRole).toUInt()); layout->setAdditionalFormats(formatRanges); } void ChatItem::doLayout(QTextLayout *layout) const { + qreal h = 0; layout->beginLayout(); - QTextLine line = layout->createLine(); - if(line.isValid()) { + forever { + QTextLine line = layout->createLine(); + if(!line.isValid()) + break; line.setLineWidth(width()); - line.setPosition(QPointF(0,0)); + line.setPosition(QPointF(0, h)); + h += line.height(); } layout->endLayout(); } +void ChatItem::paintBackground(QPainter *painter) { + painter->setClipRect(boundingRect()); // no idea why QGraphicsItem clipping won't work + + QVariant bgBrush; + if(_selectionMode == FullSelection) + bgBrush = data(ChatLineModel::SelectedBackgroundRole); + else + bgBrush = data(ChatLineModel::BackgroundRole); + if(bgBrush.isValid()) + painter->fillRect(boundingRect(), bgBrush.value()); +} // 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); - painter->setClipRect(boundingRect()); // no idea why QGraphicsItem clipping won't work - QVector formats = additionalFormats(); - QTextLayout::FormatRange selectFmt = selectionFormat(); - if(selectFmt.format.isValid()) formats.append(selectFmt); + paintBackground(painter); + QTextLayout layout; initLayout(&layout); - layout.draw(painter, QPointF(0,0), formats, boundingRect()); + layout.draw(painter, QPointF(0,0), selectionFormats(), boundingRect()); // layout()->draw(painter, QPointF(0,0), formats, boundingRect()); @@ -126,19 +154,30 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, // painter->drawRect(_boundingRect.adjusted(0, 0, -1, -1)); } -qint16 ChatItem::posToCursor(const QPointF &pos) const { - if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length(); - if(pos.y() < 0) return 0; +QVector ChatItem::selectionFormats() const { + if(!hasSelection()) + return QVector(); - QTextLayout layout; - initLayout(&layout); - 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); - } + int start, end; + if(_selectionMode == FullSelection) { + start = 0; + end = data(MessageModel::DisplayRole).toString().length(); + } else { + start = qMin(_selectionStart, _selectionEnd); + end = qMax(_selectionStart, _selectionEnd); } - return 0; + + UiStyle::FormatList fmtList = data(MessageModel::FormatRole).value(); + + while(fmtList.count() >=2 && fmtList.at(1).first <= start) + fmtList.removeFirst(); + + fmtList.first().first = start; + + while(fmtList.count() >= 2 && fmtList.last().first >= end) + fmtList.removeLast(); + + return QtUi::style()->toTextLayoutList(fmtList, end, UiStyle::Selected|data(ChatLineModel::MsgLabelRole).toUInt()).toVector(); } bool ChatItem::hasSelection() const { @@ -195,25 +234,6 @@ bool ChatItem::isPosOverSelection(const QPointF &pos) const { return false; } -QTextLayout::FormatRange ChatItem::selectionFormat() const { - QTextLayout::FormatRange selectFmt; - if(_selectionMode != NoSelection) { - 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(); - } - } else { - selectFmt.start = -1; - selectFmt.length = 0; - } - return selectFmt; -} - QList ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity caseSensitive) { QList resultList; const QAbstractItemModel *model_ = model(); @@ -298,8 +318,8 @@ void ChatItem::addActionsToMenu(QMenu *menu, const QPointF &pos) { void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); + paintBackground(painter); - painter->setClipRect(boundingRect()); // no idea why QGraphicsItem clipping won't work QTextLayout layout; initLayout(&layout); qreal layoutWidth = layout.minimumWidth(); @@ -309,15 +329,13 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op else offset = qMax(layoutWidth - width(), (qreal)0); - QTextLayout::FormatRange selectFmt = selectionFormat(); - 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), QVector() << selectFmt); + layout.draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), selectionFormats()); pixPainter.end(); // Create alpha channel mask @@ -339,7 +357,7 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op pixmap.setAlphaChannel(mask); painter->drawPixmap(0, 0, pixmap); } else { - layout.draw(painter, QPointF(0,0), QVector() << selectFmt, boundingRect()); + layout.draw(painter, QPointF(0,0), selectionFormats(), boundingRect()); } } @@ -353,13 +371,13 @@ ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGrap : 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().at(0).second); - setGeometryByWidth(width); } +QFontMetricsF *ContentsChatItem::fontMetrics() const { + return QtUi::style()->fontMetrics(data(ChatLineModel::FormatRole).value().at(0).second); +} + ContentsChatItem::~ContentsChatItem() { delete _data; } @@ -373,47 +391,28 @@ ContentsChatItemPrivate *ContentsChatItem::privateData() const { } qreal ContentsChatItem::setGeometryByWidth(qreal w) { - if(w != width()) { + if(w == width()) { + //qDebug() << Q_FUNC_INFO << "Geometry change requested with identical width!"; + } + // We use this for reloading layout info as well + //if(w != width()) { prepareGeometryChange(); setWidth(w); - // compute height - int lines = 1; - WrapColumnFinder finder(this); - while(finder.nextWrapColumn() > 0) - lines++; - setHeight(lines * fontMetrics()->lineSpacing()); + QTextLayout layout; + initLayout(&layout); + setHeight(layout.boundingRect().height()); delete _data; _data = 0; - } + //} return height(); } -void ContentsChatItem::doLayout(QTextLayout *layout) const { - ChatLineModel::WrapList wrapList = data(ChatLineModel::WrapListRole).value(); - 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::findClickables() const { // For matching URLs static QString urlEnd("(?:>|[,.;:\"]*\\s|\\b|$)"); - static QString urlChars("(?:[,.;:]*[\\w\\-~@/?&=+$()!%#*|{}\\[\\]'])"); + static QString urlChars("(?:[,.;:]*[\\w\\-~@/?&=+$()!%#*|{}\\[\\]'^])"); static QRegExp regExp[] = { // URL @@ -670,66 +669,3 @@ void ContentsChatItem::clearWebPreview() { /*************************************************************************************************/ -ContentsChatItem::WrapColumnFinder::WrapColumnFinder(const ChatItem *_item) - : item(_item), - wrapList(item->data(ChatLineModel::WrapListRole).value()), - wordidx(0), - lineCount(0), - choppedTrailing(0) -{ -} - -ContentsChatItem::WrapColumnFinder::~WrapColumnFinder() { -} - -qint16 ContentsChatItem::WrapColumnFinder::nextWrapColumn() { - if(wordidx >= wrapList.count()) - return -1; - - lineCount++; - qreal targetWidth = lineCount * item->width() + choppedTrailing; - - qint16 start = wordidx; - qint16 end = wrapList.count() - 1; - - // check if the whole line fits - if(wrapList.at(end).endX <= targetWidth) // || start == end) - return -1; - - // check if we have a very long word that needs inter word wrap - if(wrapList.at(start).endX > targetWidth) { - if(!line.isValid()) { - item->initLayoutHelper(&layout, QTextOption::NoWrap); - layout.beginLayout(); - line = layout.createLine(); - layout.endLayout(); - } - return line.xToCursor(targetWidth, QTextLine::CursorOnCharacter); - } - - while(true) { - if(start + 1 == end) { - wordidx = end; - const ChatLineModel::Word &lastWord = wrapList.at(start); // the last word we were able to squeeze in - - // both cases should be cought preliminary - Q_ASSERT(lastWord.endX <= targetWidth); // ensure that "start" really fits in - Q_ASSERT(end < wrapList.count()); // ensure that start isn't the last word - - choppedTrailing += lastWord.trailing - (targetWidth - lastWord.endX); - return wrapList.at(wordidx).start; - } - - qint16 pivot = (end + start) / 2; - if(wrapList.at(pivot).endX > targetWidth) { - end = pivot; - } else { - start = pivot; - } - } - Q_ASSERT(false); - return -1; -} - -/*************************************************************************************************/ -