X-Git-Url: https://git.quassel-irc.org/?a=blobdiff_plain;f=src%2Fqtui%2Fchatitem.cpp;h=9e7e1eb687f41cf14d2e2e0fd1702b659bd99130;hb=e7494078ad676d9fd14fab0396f51608a3ad46dc;hp=e72cbdc5af9bcdf642ac786cf01afdf73f38af50;hpb=2e023191fb47f5cbd186f7274f8ee1b5d1cc94c3;p=quassel.git diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index e72cbdc5..9e7e1eb6 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -26,6 +26,8 @@ #include #include #include +#include + #include "chatitem.h" #include "chatlinemodel.h" @@ -101,25 +103,9 @@ ChatItemPrivate *ChatItem::privateData() const { 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 - //if(_selectionMode == FullSelection) { - //painter->save(); - //painter->fillRect(boundingRect(), QApplication::palette().brush(QPalette::Highlight)); - //painter->restore(); - //} QVector formats = additionalFormats(); - if(_selectionMode != NoSelection) { - QTextLayout::FormatRange selectFmt; - 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(); - } - formats.append(selectFmt); - } + QTextLayout::FormatRange selectFmt = selectionFormat(); + if(selectFmt.format.isValid()) formats.append(selectFmt); layout()->draw(painter, QPointF(0,0), formats, boundingRect()); // Debuging Stuff @@ -150,7 +136,7 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, // painter->drawRect(_boundingRect.adjusted(0, 0, -1, -1)); } -qint16 ChatItem::posToCursor(const QPointF &pos) { +qint16 ChatItem::posToCursor(const QPointF &pos) const { if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length(); if(pos.y() < 0) return 0; for(int l = layout()->lineCount() - 1; l >= 0; l--) { @@ -162,6 +148,30 @@ qint16 ChatItem::posToCursor(const QPointF &pos) { return 0; } +bool ChatItem::hasSelection() const { + if(_selectionMode == NoSelection) + return false; + if(_selectionMode == FullSelection) + return true; + // partial + return _selectionStart != _selectionEnd; +} + +QString ChatItem::selection() const { + if(_selectionMode == FullSelection) + return data(MessageModel::DisplayRole).toString(); + if(_selectionMode == PartialSelection) + return data(MessageModel::DisplayRole).toString().mid(qMin(_selectionStart, _selectionEnd), qAbs(_selectionStart - _selectionEnd)); + return QString(); +} + +void ChatItem::setSelection(SelectionMode mode, qint16 start, qint16 end) { + _selectionMode = mode; + _selectionStart = start; + _selectionEnd = end; + update(); +} + void ChatItem::setFullSelection() { if(_selectionMode != FullSelection) { _selectionMode = FullSelection; @@ -170,8 +180,10 @@ void ChatItem::setFullSelection() { } void ChatItem::clearSelection() { - _selectionMode = NoSelection; - update(); + if(_selectionMode != NoSelection) { + _selectionMode = NoSelection; + update(); + } } void ChatItem::continueSelecting(const QPointF &pos) { @@ -180,6 +192,35 @@ void ChatItem::continueSelecting(const QPointF &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; +} + +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(); @@ -210,15 +251,13 @@ QList ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity return resultList; } -void ChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if(event->buttons() == Qt::LeftButton) { +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(event->pos()); + _selectionStart = _selectionEnd = posToCursor(pos); _selectionMode = NoSelection; // will be set to PartialSelection by mouseMoveEvent update(); - event->accept(); - } else { - event->ignore(); } } @@ -241,22 +280,70 @@ void ChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { } } +void ChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + if(event->buttons() == Qt::LeftButton) + event->accept(); + else + event->ignore(); +} + void ChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if(_selectionMode != NoSelection && !event->buttons() & Qt::LeftButton) { - _selectionEnd = posToCursor(event->pos()); - QString selection - = data(MessageModel::DisplayRole).toString().mid(qMin(_selectionStart, _selectionEnd), qAbs(_selectionStart - _selectionEnd)); - chatScene()->putToClipboard(selection); + chatScene()->selectionToClipboard(QClipboard::Selection); event->accept(); - } else { + } else event->ignore(); - } } // ************************************************************ // SenderChatItem // ************************************************************ +void SenderChatItem::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 + 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); + + 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); + 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), QVector() << selectFmt, boundingRect()); + } +} + // ************************************************************ // ContentsChatItem // ************************************************************ @@ -272,6 +359,7 @@ ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGrap qreal ContentsChatItem::setGeometryByWidth(qreal w) { if(w != width()) { + prepareGeometryChange(); setWidth(w); // compute height int lines = 1; @@ -308,16 +396,16 @@ void ContentsChatItem::doLayout() { QList ContentsChatItem::findClickables() const { // For matching URLs static QString urlEnd("(?:>|[,.;:\"]*\\s|\\b|$)"); - static QString urlChars("(?:[\\w\\-~@/?&=+$()!%#]|[,.;:]\\w)"); + 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://|mailto:)|www)%1+)%2").arg(urlChars, urlEnd)), + QRegExp(QString("((?:(?:https?://|s?ftp://|irc://|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") + 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 }; @@ -391,22 +479,16 @@ void ContentsChatItem::endHoverMode() { } } -void ContentsChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - privateData()->hasDragged = false; - ChatItem::mousePressEvent(event); -} - -void ContentsChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if(!event->buttons() && !privateData()->hasDragged) { - // got a click +void ContentsChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clickMode) { + if(clickMode == ChatScene::SingleClick) { 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); + if(!str.contains("://")) + str = "http://" + str; + QDesktopServices::openUrl(QUrl::fromEncoded(str.toAscii())); break; case Clickable::Channel: // TODO join or whatever... @@ -415,17 +497,33 @@ void ContentsChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { break; } } + } else if(clickMode == ChatScene::DoubleClick) { + chatScene()->setSelectingItem(this); + setSelectionMode(PartialSelection); + Clickable click = privateData()->currentClickable; + if(click.isValid()) { + setSelectionStart(click.start); + setSelectionEnd(click.start + click.length); + } else { + // find word boundary + QString str = data(ChatLineModel::DisplayRole).toString(); + qint16 cursor = posToCursor(pos); + qint16 start = str.lastIndexOf(QRegExp("\\W"), cursor) + 1; + qint16 end = qMin(str.indexOf(QRegExp("\\W"), cursor), str.length()); + if(end < 0) end = str.length(); + setSelectionStart(start); + setSelectionEnd(end); + } + update(); + } else if(clickMode == ChatScene::TripleClick) { + setSelection(PartialSelection, 0, data(ChatLineModel::DisplayRole).toString().length()); } - ChatItem::mouseReleaseEvent(event); + ChatItem::handleClick(pos, clickMode); } 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); } @@ -442,10 +540,10 @@ void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { if(idx >= click.start && idx < click.start + click.length) { if(click.type == Clickable::Url) { onClickable = true; - showWebPreview(click); + 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 + // onClickable = true; //FIXME disabled for now } if(onClickable) { setCursor(Qt::PointingHandCursor); @@ -459,8 +557,35 @@ void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { event->accept(); } +/* +void ContentsChatItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { + 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) { + QMenu menu; + QAction *copyToClipboard = menu.addAction(QObject::tr("Copy to Clipboard")); + QAction *selected = menu.exec(event->screenPos()); + if(selected == copyToClipboard) { + QString url = data(ChatLineModel::DisplayRole).toString().mid(click.start, click.length); +# ifdef Q_WS_X11 + QApplication::clipboard()->setText(url, QClipboard::Selection); +# endif +//# else + QApplication::clipboard()->setText(url); +//# endif + } + } + } + } +} +*/ + void ContentsChatItem::showWebPreview(const Clickable &click) { -#ifdef HAVE_WEBKIT +#ifndef HAVE_WEBKIT + Q_UNUSED(click); +#else QTextLine line = layout()->lineForTextPosition(click.start); qreal x = line.cursorToX(click.start); qreal width = line.cursorToX(click.start + click.length) - x;