From: Manuel Nickschas Date: Fri, 24 Jul 2009 04:06:40 +0000 (+0200) Subject: Make text selection stylesheetable X-Git-Tag: 0.5-rc1~124 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=c6a6c0e4317986792320826956fd5ead2b3e9e67 Make text selection stylesheetable This adds a new [label="selected"] that you can use in stylesheets to determine how a text selection should look like in ChatView (i.e. it's no longer hardcoded to the "highlight" and "highlighted-text" Palette roles). Note that you probably shouldn't do more than setting the selection colors globally for ChatLine, as else the painting routines might do funky stuff. --- diff --git a/src/qtui/chatitem.cpp b/src/qtui/chatitem.cpp index 290542be..2297983b 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); @@ -83,23 +98,27 @@ void ChatItem::doLayout(QTextLayout *layout) const { 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 + paintBackground(painter); - QVariant bgBrush = data(ChatLineModel::BackgroundRole); - if(bgBrush.isValid()) - painter->fillRect(boundingRect(), bgBrush.value()); - - QVector formats = additionalFormats(); - QTextLayout::FormatRange selectFmt = selectionFormat(); - if(selectFmt.format.isValid()) formats.append(selectFmt); 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()); @@ -131,19 +150,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 { @@ -200,25 +230,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(); @@ -303,12 +314,7 @@ void ChatItem::addActionsToMenu(QMenu *menu, const QPointF &pos) { 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 - - QVariant bgBrush = data(ChatLineModel::BackgroundRole); - if(bgBrush.isValid()) - painter->fillRect(boundingRect(), bgBrush.value()); + paintBackground(painter); QTextLayout layout; initLayout(&layout); @@ -319,15 +325,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 @@ -349,7 +353,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()); } } diff --git a/src/qtui/chatitem.h b/src/qtui/chatitem.h index 5796f5e9..efd0705c 100644 --- a/src/qtui/chatitem.h +++ b/src/qtui/chatitem.h @@ -83,7 +83,8 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - virtual QTextLayout::FormatRange selectionFormat() const; + void paintBackground(QPainter *); + QVector selectionFormats() const; virtual inline QVector additionalFormats() const { return QVector(); } inline qint16 selectionStart() const { return _selectionStart; } diff --git a/src/qtui/chatline.cpp b/src/qtui/chatline.cpp index 1c7db84c..f6ea50f9 100644 --- a/src/qtui/chatline.cpp +++ b/src/qtui/chatline.cpp @@ -165,11 +165,13 @@ void ChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, painter->fillRect(boundingRect(), msgFmt.background()); } - // TODO make this dependent on the style engine (highlight-color & friends) if(_selection & Selected) { - qreal left = item((ChatLineModel::ColumnType)(_selection & ItemMask)).x(); - QRectF selectRect(left, 0, width() - left, height()); - painter->fillRect(selectRect, QApplication::palette().brush(QPalette::Highlight)); + QTextCharFormat selFmt = QtUi::style()->format(UiStyle::formatType(type), label | UiStyle::Selected); + if(selFmt.hasProperty(QTextFormat::BackgroundBrush)) { + qreal left = item((ChatLineModel::ColumnType)(_selection & ItemMask)).x(); + QRectF selectRect(left, 0, width() - left, height()); + painter->fillRect(selectRect, selFmt.background()); + } } // new line marker diff --git a/src/qtui/chatlinemodel.h b/src/qtui/chatlinemodel.h index dffb7065..74f0aceb 100644 --- a/src/qtui/chatlinemodel.h +++ b/src/qtui/chatlinemodel.h @@ -32,7 +32,8 @@ class ChatLineModel : public MessageModel { public: enum ChatLineRole { WrapListRole = MessageModel::UserRole, - MsgLabelRole + MsgLabelRole, + SelectedBackgroundRole }; ChatLineModel(QObject *parent = 0); diff --git a/src/qtui/chatlinemodelitem.cpp b/src/qtui/chatlinemodelitem.cpp index 1653962e..343fb992 100644 --- a/src/qtui/chatlinemodelitem.cpp +++ b/src/qtui/chatlinemodelitem.cpp @@ -84,6 +84,8 @@ QVariant ChatLineModelItem::timestampData(int role) const { return _styledMsg.timestamp(); case ChatLineModel::BackgroundRole: return backgroundBrush(UiStyle::Timestamp); + case ChatLineModel::SelectedBackgroundRole: + return backgroundBrush(UiStyle::Timestamp, true); case ChatLineModel::FormatRole: return QVariant::fromValue(UiStyle::FormatList() << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Timestamp)); @@ -99,6 +101,8 @@ QVariant ChatLineModelItem::senderData(int role) const { return _styledMsg.plainSender(); case ChatLineModel::BackgroundRole: return backgroundBrush(UiStyle::Sender); + case ChatLineModel::SelectedBackgroundRole: + return backgroundBrush(UiStyle::Sender, true); case ChatLineModel::FormatRole: return QVariant::fromValue(UiStyle::FormatList() << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Sender)); @@ -113,6 +117,8 @@ QVariant ChatLineModelItem::contentsData(int role) const { return _styledMsg.plainContents(); case ChatLineModel::BackgroundRole: return backgroundBrush(UiStyle::Contents); + case ChatLineModel::SelectedBackgroundRole: + return backgroundBrush(UiStyle::Contents, true); case ChatLineModel::FormatRole: return QVariant::fromValue(_styledMsg.contentsFormatList()); case ChatLineModel::WrapListRole: @@ -132,8 +138,8 @@ quint32 ChatLineModelItem::messageLabel() const { return label; } -QVariant ChatLineModelItem::backgroundBrush(UiStyle::FormatType subelement) const { - QTextCharFormat fmt = QtUi::style()->format(UiStyle::formatType(_styledMsg.type()) | subelement, messageLabel()); +QVariant ChatLineModelItem::backgroundBrush(UiStyle::FormatType subelement, bool selected) const { + QTextCharFormat fmt = QtUi::style()->format(UiStyle::formatType(_styledMsg.type()) | subelement, messageLabel() | (selected ? UiStyle::Selected : 0)); if(fmt.hasProperty(QTextFormat::BackgroundBrush)) return QVariant::fromValue(fmt.background()); return QVariant(); diff --git a/src/qtui/chatlinemodelitem.h b/src/qtui/chatlinemodelitem.h index a553cca3..b7a3a858 100644 --- a/src/qtui/chatlinemodelitem.h +++ b/src/qtui/chatlinemodelitem.h @@ -53,7 +53,7 @@ private: QVariant senderData(int role) const; QVariant contentsData(int role) const; - QVariant backgroundBrush(UiStyle::FormatType subelement) const; + QVariant backgroundBrush(UiStyle::FormatType subelement, bool selected = false) const; quint32 messageLabel() const; void computeWrapList() const; diff --git a/src/qtui/chatview.cpp b/src/qtui/chatview.cpp index eac76a20..3c7d8cf9 100644 --- a/src/qtui/chatview.cpp +++ b/src/qtui/chatview.cpp @@ -174,6 +174,8 @@ void ChatView::verticalScrollbarChanged(int newPos) { void ChatView::styleChanged() { invalidateScene(); + if(scene()) + scene()->update(); } MsgId ChatView::lastMsgId() const { diff --git a/src/uisupport/qssparser.cpp b/src/uisupport/qssparser.cpp index c6b69d9e..b0e84b8a 100644 --- a/src/uisupport/qssparser.cpp +++ b/src/uisupport/qssparser.cpp @@ -212,6 +212,8 @@ quint64 QssParser::parseFormatType(const QString &decl) { quint64 labeltype = 0; if(condValue == "highlight") labeltype = UiStyle::Highlight; + else if(condValue == "selected") + labeltype = UiStyle::Selected; else { qWarning() << Q_FUNC_INFO << tr("Invalid message label: %1").arg(condValue); return UiStyle::Invalid;