X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fqtui%2Fchatitem.cpp;h=be792c97917e7327e60e316e27817b883a5261ed;hp=0900c7e2168caed9997bc98a4a492ff910be4fba;hb=7ea9c498bfb975a95c11d6ddc28835840cf3353d;hpb=9ce9c0ab3ac6f4bda4e3a70bf13a9c07d2debfe6 diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index 0900c7e2..be792c97 100644 --- a/src/qtui/chatitem.cpp +++ b/src/qtui/chatitem.cpp @@ -28,9 +28,11 @@ #include #include - +#include "bufferview.h" #include "chatitem.h" #include "chatlinemodel.h" +#include "iconloader.h" +#include "mainwin.h" #include "qtui.h" #include "qtuistyle.h" @@ -165,6 +167,13 @@ QString ChatItem::selection() const { 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; @@ -173,8 +182,10 @@ void ChatItem::setFullSelection() { } void ChatItem::clearSelection() { - _selectionMode = NoSelection; - update(); + if(_selectionMode != NoSelection) { + _selectionMode = NoSelection; + update(); + } } void ChatItem::continueSelecting(const QPointF &pos) { @@ -187,7 +198,7 @@ bool ChatItem::isPosOverSelection(const QPointF &pos) const { if(_selectionMode == FullSelection) return true; if(_selectionMode == PartialSelection) { - int cursor = posToCursor(pos); qDebug() << cursor << _selectionStart << _selectionEnd; + int cursor = posToCursor(pos); return cursor >= qMin(_selectionStart, _selectionEnd) && cursor <= qMax(_selectionStart, _selectionEnd); } return false; @@ -243,7 +254,8 @@ QList ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity } void ChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clickMode) { - if(clickMode == ChatScene::SingleClick) { + // single clicks are already handled by the scene (for clearing the selection) + if(clickMode == ChatScene::DragStartClick) { chatScene()->setSelectingItem(this); _selectionStart = _selectionEnd = posToCursor(pos); _selectionMode = NoSelection; // will be set to PartialSelection by mouseMoveEvent @@ -271,23 +283,24 @@ void ChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { } void ChatItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if(event->buttons() == Qt::LeftButton) { + if(event->buttons() == Qt::LeftButton) event->accept(); - } else { + 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(); - } +} + +void ChatItem::addActionsToMenu(QMenu *menu, const QPointF &pos) { + Q_UNUSED(menu); + Q_UNUSED(pos); + } // ************************************************************ @@ -342,6 +355,9 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op // ************************************************************ // ContentsChatItem // ************************************************************ + +ContentsChatItem::ActionProxy ContentsChatItem::_actionProxy; + ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent) : ChatItem(0, 0, pos, parent) { @@ -396,7 +412,7 @@ QList ContentsChatItem::findClickables() const { 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), Qt::CaseInsensitive), + 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. @@ -449,6 +465,16 @@ QList ContentsChatItem::findClickables() const { return result; } +ContentsChatItem::Clickable ContentsChatItem::clickableAt(const QPointF &pos) const { + qint16 idx = posToCursor(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) + return click; + } + return Clickable(); +} + QVector ContentsChatItem::additionalFormats() const { // mark a clickable if hovered upon QVector fmt; @@ -474,22 +500,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 - Clickable click = privateData()->currentClickable; +void ContentsChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clickMode) { + if(clickMode == ChatScene::SingleClick) { + Clickable click = clickableAt(pos); 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(QUrl::fromEncoded(str.toAscii())); + if(!str.contains("://")) + str = "http://" + str; + QDesktopServices::openUrl(QUrl::fromEncoded(str.toUtf8(), QUrl::TolerantMode)); break; case Clickable::Channel: // TODO join or whatever... @@ -498,17 +518,33 @@ void ContentsChatItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { break; } } + } else if(clickMode == ChatScene::DoubleClick) { + chatScene()->setSelectingItem(this); + setSelectionMode(PartialSelection); + Clickable click = clickableAt(pos); + 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); } @@ -519,52 +555,70 @@ void ContentsChatItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { 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) { + Clickable click = clickableAt(event->pos()); + if(click.isValid()) { + if(click.type == Clickable::Url) { + onClickable = true; + showWebPreview(click); + } else if(click.type == Clickable::Channel) { + // don't make clickable if it's our own name + QString name = data(ChatLineModel::DisplayRole).toString().mid(click.start, click.length); + BufferId myId = data(MessageModel::BufferIdRole).value(); + if(Client::networkModel()->bufferName(myId) != name) 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) { + setCursor(Qt::PointingHandCursor); + privateData()->currentClickable = click; + update(); + return; } } if(!onClickable) endHoverMode(); 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::addActionsToMenu(QMenu *menu, const QPointF &pos) { + Q_UNUSED(pos); // we assume that the current mouse cursor pos is the point of invocation + + if(privateData()->currentClickable.isValid()) { + Clickable click = privateData()->currentClickable; + switch(click.type) { + case Clickable::Url: + privateData()->activeClickable = click; + menu->addAction(SmallIcon("edit-copy"), tr("Copy Link Address"), + &_actionProxy, SLOT(copyLinkToClipboard()))->setData(QVariant::fromValue(this)); + break; + case Clickable::Channel: { + // Hide existing menu actions, they confuse us when right-clicking on a clickable + foreach(QAction *action, menu->actions()) + action->setVisible(false); + QString name = data(ChatLineModel::DisplayRole).toString().mid(click.start, click.length); + Client::mainUi()->actionProvider()->addActions(menu, chatScene()->filter(), data(MessageModel::BufferIdRole).value(), name); + break; } + default: + break; } + } else { + + // Buffer-specific actions + Client::mainUi()->actionProvider()->addActions(menu, chatScene()->filter(), data(MessageModel::BufferIdRole).value()); + } +} + +void ContentsChatItem::copyLinkToClipboard() { + Clickable click = privateData()->activeClickable; + if(click.isValid() && click.type == Clickable::Url) { + QString url = data(ChatLineModel::DisplayRole).toString().mid(click.start, click.length); + if(!url.contains("://")) + url = "http://" + url; + chatScene()->stringToClipboard(url); } } +/******** WEB PREVIEW *****************************************************************************/ + void ContentsChatItem::showWebPreview(const Clickable &click) { #ifndef HAVE_WEBKIT Q_UNUSED(click); @@ -656,3 +710,5 @@ qint16 ContentsChatItem::WrapColumnFinder::nextWrapColumn() { return -1; } +/*************************************************************************************************/ +