Make text selection stylesheetable
authorManuel Nickschas <sputnick@quassel-irc.org>
Fri, 24 Jul 2009 04:06:40 +0000 (06:06 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Thu, 6 Aug 2009 18:25:05 +0000 (20:25 +0200)
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.

src/qtui/chatitem.cpp
src/qtui/chatitem.h
src/qtui/chatline.cpp
src/qtui/chatlinemodel.h
src/qtui/chatlinemodelitem.cpp
src/qtui/chatlinemodelitem.h
src/qtui/chatview.cpp
src/uisupport/qssparser.cpp

index 290542b..2297983 100644 (file)
@@ -58,6 +58,21 @@ QVariant ChatItem::data(int role) const {
   return model()->data(index, role);
 }
 
   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);
 
 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();
 }
 
   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<QBrush>());
+}
 
 // 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);
 
 // 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<QBrush>());
-
-  QVector<QTextLayout::FormatRange> formats = additionalFormats();
-  QTextLayout::FormatRange selectFmt = selectionFormat();
-  if(selectFmt.format.isValid()) formats.append(selectFmt);
   QTextLayout layout;
   initLayout(&layout);
   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());
 
 
   //  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));
 }
 
 //   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<QTextLayout::FormatRange> ChatItem::selectionFormats() const {
+  if(!hasSelection())
+    return QVector<QTextLayout::FormatRange>();
 
 
-  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<UiStyle::FormatList>();
+
+  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 {
 }
 
 bool ChatItem::hasSelection() const {
@@ -200,25 +230,6 @@ bool ChatItem::isPosOverSelection(const QPointF &pos) const {
   return false;
 }
 
   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<QRectF> ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity caseSensitive) {
   QList<QRectF> resultList;
   const QAbstractItemModel *model_ = model();
 QList<QRectF> ChatItem::findWords(const QString &searchWord, Qt::CaseSensitivity caseSensitive) {
   QList<QRectF> 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);
 
 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<QBrush>());
+  paintBackground(painter);
 
   QTextLayout layout;
   initLayout(&layout);
 
   QTextLayout layout;
   initLayout(&layout);
@@ -319,15 +325,13 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op
   else
     offset = qMax(layoutWidth - width(), (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);
   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<QTextLayout::FormatRange>() << selectFmt);
+    layout.draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), selectionFormats());
     pixPainter.end();
 
     // Create alpha channel mask
     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 {
     pixmap.setAlphaChannel(mask);
     painter->drawPixmap(0, 0, pixmap);
   } else {
-    layout.draw(painter, QPointF(0,0), QVector<QTextLayout::FormatRange>() << selectFmt, boundingRect());
+    layout.draw(painter, QPointF(0,0), selectionFormats(), boundingRect());
   }
 }
 
   }
 }
 
index 5796f5e..efd0705 100644 (file)
@@ -83,7 +83,8 @@ protected:
   virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
   virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
 
   virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
   virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
 
-  virtual QTextLayout::FormatRange selectionFormat() const;
+  void paintBackground(QPainter *);
+  QVector<QTextLayout::FormatRange> selectionFormats() const;
   virtual inline QVector<QTextLayout::FormatRange> additionalFormats() const { return QVector<QTextLayout::FormatRange>(); }
 
   inline qint16 selectionStart() const { return _selectionStart; }
   virtual inline QVector<QTextLayout::FormatRange> additionalFormats() const { return QVector<QTextLayout::FormatRange>(); }
 
   inline qint16 selectionStart() const { return _selectionStart; }
index 1c7db84..f6ea50f 100644 (file)
@@ -165,11 +165,13 @@ void ChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
     painter->fillRect(boundingRect(), msgFmt.background());
   }
 
     painter->fillRect(boundingRect(), msgFmt.background());
   }
 
-  // TODO make this dependent on the style engine (highlight-color & friends)
   if(_selection & Selected) {
   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
   }
 
   // new line marker
index dffb706..74f0ace 100644 (file)
@@ -32,7 +32,8 @@ class ChatLineModel : public MessageModel {
 public:
   enum ChatLineRole {
     WrapListRole = MessageModel::UserRole,
 public:
   enum ChatLineRole {
     WrapListRole = MessageModel::UserRole,
-    MsgLabelRole
+    MsgLabelRole,
+    SelectedBackgroundRole
   };
 
   ChatLineModel(QObject *parent = 0);
   };
 
   ChatLineModel(QObject *parent = 0);
index 1653962..343fb99 100644 (file)
@@ -84,6 +84,8 @@ QVariant ChatLineModelItem::timestampData(int role) const {
     return _styledMsg.timestamp();
   case ChatLineModel::BackgroundRole:
     return backgroundBrush(UiStyle::Timestamp);
     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>(UiStyle::FormatList()
                       << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Timestamp));
   case ChatLineModel::FormatRole:
     return QVariant::fromValue<UiStyle::FormatList>(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);
     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>(UiStyle::FormatList()
                       << qMakePair((quint16)0, (quint32)UiStyle::formatType(_styledMsg.type()) | UiStyle::Sender));
   case ChatLineModel::FormatRole:
     return QVariant::fromValue<UiStyle::FormatList>(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);
     return _styledMsg.plainContents();
   case ChatLineModel::BackgroundRole:
     return backgroundBrush(UiStyle::Contents);
+  case ChatLineModel::SelectedBackgroundRole:
+    return backgroundBrush(UiStyle::Contents, true);
   case ChatLineModel::FormatRole:
     return QVariant::fromValue<UiStyle::FormatList>(_styledMsg.contentsFormatList());
   case ChatLineModel::WrapListRole:
   case ChatLineModel::FormatRole:
     return QVariant::fromValue<UiStyle::FormatList>(_styledMsg.contentsFormatList());
   case ChatLineModel::WrapListRole:
@@ -132,8 +138,8 @@ quint32 ChatLineModelItem::messageLabel() const {
   return label;
 }
 
   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<QBrush>(fmt.background());
   return QVariant();
   if(fmt.hasProperty(QTextFormat::BackgroundBrush))
     return QVariant::fromValue<QBrush>(fmt.background());
   return QVariant();
index a553cca..b7a3a85 100644 (file)
@@ -53,7 +53,7 @@ private:
   QVariant senderData(int role) const;
   QVariant contentsData(int role) const;
 
   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;
   quint32 messageLabel() const;
 
   void computeWrapList() const;
index eac76a2..3c7d8cf 100644 (file)
@@ -174,6 +174,8 @@ void ChatView::verticalScrollbarChanged(int newPos) {
 
 void ChatView::styleChanged() {
   invalidateScene();
 
 void ChatView::styleChanged() {
   invalidateScene();
+  if(scene())
+    scene()->update();
 }
 
 MsgId ChatView::lastMsgId() const {
 }
 
 MsgId ChatView::lastMsgId() const {
index c6b69d9..b0e84b8 100644 (file)
@@ -212,6 +212,8 @@ quint64 QssParser::parseFormatType(const QString &decl) {
         quint64 labeltype = 0;
         if(condValue == "highlight")
           labeltype = UiStyle::Highlight;
         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;
         else {
           qWarning() << Q_FUNC_INFO << tr("Invalid message label: %1").arg(condValue);
           return UiStyle::Invalid;