Make URLs stylable and fix on-hover underline
authorManuel Nickschas <sputnick@quassel-irc.org>
Tue, 11 Aug 2009 21:45:56 +0000 (23:45 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Tue, 11 Aug 2009 21:45:56 +0000 (23:45 +0200)
You can now use ::url in stylesheets to define how URLs should look like.
Default is palette(link) which uses the system color for that purpose. Since this
is applied after layouting, you shouldn't use attributes that change the font geometry.

data/default.qss
src/qtui/chatitem.cpp
src/qtui/chatitem.h
src/uisupport/qssparser.cpp
src/uisupport/uistyle.cpp

index 22d2401..0371fac 100644 (file)
@@ -27,6 +27,10 @@ ChatLine::timestamp {
   foreground: #707c70;
 }
 
   foreground: #707c70;
 }
 
+ChatLine::url {
+  foreground: palette(link);
+}
+
 Palette {
   marker-line: red;
 }
 Palette {
   marker-line: red;
 }
index 1f7e9ac..e2fa55b 100644 (file)
@@ -84,10 +84,14 @@ void ChatItem::initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode wrapM
   layout->setTextOption(option);
 
   QList<QTextLayout::FormatRange> formatRanges
   layout->setTextOption(option);
 
   QList<QTextLayout::FormatRange> formatRanges
-         = QtUi::style()->toTextLayoutList(data(MessageModel::FormatRole).value<UiStyle::FormatList>(), layout->text().length(), data(ChatLineModel::MsgLabelRole).toUInt());
+         = QtUi::style()->toTextLayoutList(formatList(), layout->text().length(), data(ChatLineModel::MsgLabelRole).toUInt());
   layout->setAdditionalFormats(formatRanges);
 }
 
   layout->setAdditionalFormats(formatRanges);
 }
 
+UiStyle::FormatList ChatItem::formatList() const {
+  return data(MessageModel::FormatRole).value<UiStyle::FormatList>();
+}
+
 void ChatItem::doLayout(QTextLayout *layout) const {
   layout->beginLayout();
   QTextLine line = layout->createLine();
 void ChatItem::doLayout(QTextLayout *layout) const {
   layout->beginLayout();
   QTextLine line = layout->createLine();
@@ -118,7 +122,7 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
 
   QTextLayout layout;
   initLayout(&layout);
 
   QTextLayout layout;
   initLayout(&layout);
-  layout.draw(painter, QPointF(0,0), selectionFormats(), boundingRect());
+  layout.draw(painter, QPointF(0,0), additionalFormats(), boundingRect());
 
   //  layout()->draw(painter, QPointF(0,0), formats, boundingRect());
 
 
   //  layout()->draw(painter, QPointF(0,0), formats, boundingRect());
 
@@ -150,6 +154,34 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
 //   painter->drawRect(_boundingRect.adjusted(0, 0, -1, -1));
 }
 
 //   painter->drawRect(_boundingRect.adjusted(0, 0, -1, -1));
 }
 
+void ChatItem::overlayFormat(UiStyle::FormatList &fmtList, int start, int end, quint32 overlayFmt) const {
+  for(int i = 0; i < fmtList.count(); i++) {
+    int fmtStart = fmtList.at(i).first;
+    int fmtEnd = (i < fmtList.count()-1 ? fmtList.at(i+1).first : data(MessageModel::DisplayRole).toString().length());
+
+    if(fmtEnd <= start)
+      continue;
+    if(fmtStart >= end)
+      break;
+
+    // split the format if necessary
+    if(fmtStart < start) {
+      fmtList.insert(i, fmtList.at(i));
+      fmtList[++i].first = start;
+    }
+    if(end < fmtEnd) {
+      fmtList.insert(i, fmtList.at(i));
+      fmtList[i+1].first = end;
+    }
+
+    fmtList[i].second |= overlayFmt;
+  }
+}
+
+QVector<QTextLayout::FormatRange> ChatItem::additionalFormats() const {
+  return selectionFormats();
+}
+
 QVector<QTextLayout::FormatRange> ChatItem::selectionFormats() const {
   if(!hasSelection())
     return QVector<QTextLayout::FormatRange>();
 QVector<QTextLayout::FormatRange> ChatItem::selectionFormats() const {
   if(!hasSelection())
     return QVector<QTextLayout::FormatRange>();
@@ -163,14 +195,14 @@ QVector<QTextLayout::FormatRange> ChatItem::selectionFormats() const {
     end = qMax(_selectionStart, _selectionEnd);
   }
 
     end = qMax(_selectionStart, _selectionEnd);
   }
 
-  UiStyle::FormatList fmtList = data(MessageModel::FormatRole).value<UiStyle::FormatList>();
+  UiStyle::FormatList fmtList = formatList();
 
 
-  while(fmtList.count() >=2 && fmtList.at(1).first <= start)
+  while(fmtList.count() > 1 && fmtList.at(1).first <= start)
     fmtList.removeFirst();
 
   fmtList.first().first = start;
 
     fmtList.removeFirst();
 
   fmtList.first().first = start;
 
-  while(fmtList.count() >= 2 && fmtList.last().first >= end)
+  while(fmtList.count() > 1 && fmtList.last().first >= end)
     fmtList.removeLast();
 
   return QtUi::style()->toTextLayoutList(fmtList, end, UiStyle::Selected|data(ChatLineModel::MsgLabelRole).toUInt()).toVector();
     fmtList.removeLast();
 
   return QtUi::style()->toTextLayoutList(fmtList, end, UiStyle::Selected|data(ChatLineModel::MsgLabelRole).toUInt()).toVector();
@@ -331,7 +363,7 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op
     QPixmap pixmap(layout.boundingRect().toRect().size());
     pixmap.fill(Qt::transparent);
     QPainter pixPainter(&pixmap);
     QPixmap pixmap(layout.boundingRect().toRect().size());
     pixmap.fill(Qt::transparent);
     QPainter pixPainter(&pixmap);
-    layout.draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), selectionFormats());
+    layout.draw(&pixPainter, QPointF(qMax(offset, (qreal)0), 0), additionalFormats());
     pixPainter.end();
 
     // Create alpha channel mask
     pixPainter.end();
 
     // Create alpha channel mask
@@ -353,7 +385,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), selectionFormats(), boundingRect());
+    layout.draw(painter, QPointF(0,0), additionalFormats(), boundingRect());
   }
 }
 
   }
 }
 
@@ -507,9 +539,20 @@ ContentsChatItem::Clickable ContentsChatItem::clickableAt(const QPointF &pos) co
   return Clickable();
 }
 
   return Clickable();
 }
 
+UiStyle::FormatList ContentsChatItem::formatList() const {
+  UiStyle::FormatList fmtList = ChatItem::formatList();
+  for(int i = 0; i < privateData()->clickables.count(); i++) {
+    Clickable click = privateData()->clickables.at(i);
+    if(click.type == Clickable::Url) {
+      overlayFormat(fmtList, click.start, click.start + click.length, UiStyle::Url);
+    }
+  }
+  return fmtList;
+}
+
 QVector<QTextLayout::FormatRange> ContentsChatItem::additionalFormats() const {
 QVector<QTextLayout::FormatRange> ContentsChatItem::additionalFormats() const {
+  QVector<QTextLayout::FormatRange> fmt = ChatItem::additionalFormats();
   // mark a clickable if hovered upon
   // mark a clickable if hovered upon
-  QVector<QTextLayout::FormatRange> fmt;
   if(privateData()->currentClickable.isValid()) {
     Clickable click = privateData()->currentClickable;
     QTextLayout::FormatRange f;
   if(privateData()->currentClickable.isValid()) {
     Clickable click = privateData()->currentClickable;
     QTextLayout::FormatRange f;
index e31ce81..9abd761 100644 (file)
@@ -52,6 +52,7 @@ public:
     doLayout(layout);
   }
   virtual void doLayout(QTextLayout *) const;
     doLayout(layout);
   }
   virtual void doLayout(QTextLayout *) const;
+  virtual UiStyle::FormatList formatList() const;
 
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
   enum { Type = ChatScene::ChatItemType };
 
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
   enum { Type = ChatScene::ChatItemType };
@@ -85,7 +86,8 @@ protected:
 
   void paintBackground(QPainter *);
   QVector<QTextLayout::FormatRange> selectionFormats() const;
 
   void paintBackground(QPainter *);
   QVector<QTextLayout::FormatRange> selectionFormats() const;
-  virtual inline QVector<QTextLayout::FormatRange> additionalFormats() const { return QVector<QTextLayout::FormatRange>(); }
+  virtual QVector<QTextLayout::FormatRange> additionalFormats() const;
+  void overlayFormat(UiStyle::FormatList &fmtList, int start, int end, quint32 overlayFmt) const;
 
   inline qint16 selectionStart() const { return _selectionStart; }
   inline void setSelectionStart(qint16 start) { _selectionStart = start; }
 
   inline qint16 selectionStart() const { return _selectionStart; }
   inline void setSelectionStart(qint16 start) { _selectionStart = start; }
@@ -190,6 +192,7 @@ protected:
     doLayout(layout);
   }
   virtual void doLayout(QTextLayout *layout) const;
     doLayout(layout);
   }
   virtual void doLayout(QTextLayout *layout) const;
+  virtual UiStyle::FormatList formatList() const;
 
 private:
   struct Clickable;
 
 private:
   struct Clickable;
index d8af2dc..1262be7 100644 (file)
@@ -186,6 +186,8 @@ quint64 QssParser::parseFormatType(const QString &decl) {
       fmtType |= UiStyle::Hostmask;
     else if(subElement == "modeflags")
       fmtType |= UiStyle::ModeFlags;
       fmtType |= UiStyle::Hostmask;
     else if(subElement == "modeflags")
       fmtType |= UiStyle::ModeFlags;
+    else if(subElement == "url")
+      fmtType |= UiStyle::Url;
     else {
       qWarning() << Q_FUNC_INFO << tr("Invalid subelement name in %1").arg(decl);
       return UiStyle::Invalid;
     else {
       qWarning() << Q_FUNC_INFO << tr("Invalid subelement name in %1").arg(decl);
       return UiStyle::Invalid;
index 12c3889..0ebdf06 100644 (file)
@@ -354,7 +354,7 @@ void UiStyle::mergeFormat(QTextCharFormat &fmt, quint32 ftype, quint64 label) {
 
   // URL
   if(ftype & Url)
 
   // URL
   if(ftype & Url)
-    mergeSubElementFormat(fmt, ftype & Url, label);
+    mergeSubElementFormat(fmt, ftype & (Url | 0x000000ff), label);
 }
 
 // Merge a subelement format into an existing message format
 }
 
 // Merge a subelement format into an existing message format