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;
 }
 
+ChatLine::url {
+  foreground: palette(link);
+}
+
 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
-         = 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);
 }
 
+UiStyle::FormatList ChatItem::formatList() const {
+  return data(MessageModel::FormatRole).value<UiStyle::FormatList>();
+}
+
 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);
-  layout.draw(painter, QPointF(0,0), selectionFormats(), boundingRect());
+  layout.draw(painter, QPointF(0,0), additionalFormats(), 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));
 }
 
+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>();
@@ -163,14 +195,14 @@ QVector<QTextLayout::FormatRange> ChatItem::selectionFormats() const {
     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;
 
-  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();
@@ -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);
-    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
@@ -353,7 +385,7 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op
     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();
 }
 
+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> fmt = ChatItem::additionalFormats();
   // mark a clickable if hovered upon
-  QVector<QTextLayout::FormatRange> fmt;
   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;
+  virtual UiStyle::FormatList formatList() const;
 
   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;
-  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; }
@@ -190,6 +192,7 @@ protected:
     doLayout(layout);
   }
   virtual void doLayout(QTextLayout *layout) const;
+  virtual UiStyle::FormatList formatList() const;
 
 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;
+    else if(subElement == "url")
+      fmtType |= UiStyle::Url;
     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)
-    mergeSubElementFormat(fmt, ftype & Url, label);
+    mergeSubElementFormat(fmt, ftype & (Url | 0x000000ff), label);
 }
 
 // Merge a subelement format into an existing message format