Implement proper layouting for QmlChatLine qml
authorManuel Nickschas <sputnick@quassel-irc.org>
Sat, 19 Nov 2011 17:09:20 +0000 (18:09 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Sat, 19 Nov 2011 17:09:20 +0000 (18:09 +0100)
Since ListView seems to be efficient enough to handle resizes sanely, we don't
have to resort to do manual wordwrapping and could significantly simplify our
text layouting code...

src/qmlui/qmlchatline.cpp
src/qmlui/qmlchatline.h

index 6b1665c..680dc59 100644 (file)
  *   Free Software Foundation, Inc.,                                       *
  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  ***************************************************************************/
+#include <QApplication>
 #include <QPainter>
 
+#include "graphicalui.h"
 #include "qmlchatline.h"
 
 void QmlChatLine::registerTypes() {
@@ -53,7 +55,7 @@ QmlChatLine::QmlChatLine(QDeclarativeItem *parent)
     _layout(0)
 {
   setFlag(ItemHasNoContents, false);
-  setImplicitHeight(20);
+  setImplicitHeight(QApplication::fontMetrics().height());
   setImplicitWidth(1000);
   connect(this, SIGNAL(columnWidthChanged(ColumnType)), SLOT(onColumnWidthChanged(ColumnType)));
 }
@@ -81,6 +83,10 @@ void QmlChatLine::setSenderWidth(qreal w) {
 void QmlChatLine::setContentsWidth(qreal w) {
   if(w != _contentsWidth) {
     _contentsWidth = w;
+
+    if(renderData().isValid)
+      layout()->compute();
+
     emit contentsWidthChanged(w);
     emit columnWidthChanged(ContentsColumn);
   }
@@ -135,6 +141,7 @@ QRectF QmlChatLine::columnBoundingRect(ColumnType colType) const {
 
 void QmlChatLine::setRenderData(const RenderData &data) {
   _data = data;
+
   if(_layout) {
     delete _layout;
     _layout = 0;
@@ -143,9 +150,9 @@ void QmlChatLine::setRenderData(const RenderData &data) {
   //update();
 }
 
-QmlChatLine::ColumnLayout *QmlChatLine::layout() const {
+QmlChatLine::Layout *QmlChatLine::layout() {
   if(!_layout) {
-    _layout = new ColumnLayout(this);
+    _layout = new Layout(this);
   }
   return _layout;
 }
@@ -153,44 +160,180 @@ QmlChatLine::ColumnLayout *QmlChatLine::layout() const {
 void QmlChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
   Q_UNUSED(option)
   Q_UNUSED(widget)
-  //painter->drawText(0, 0, renderData()[TimestampColumn].text);
-  //painter->drawText(timestampWidth() + columnSpacing(), 0, renderData()[SenderColumn].text);
-  //painter->drawText(timestampWidth() + senderWidth() + 2*columnSpacing(), 0, renderData()[ContentsColumn].text);
-  layout()->draw(painter);
+  if(renderData().isValid)
+    layout()->draw(painter);
 }
 
 void QmlChatLine::onColumnWidthChanged(ColumnType colType) {
-
-  //qDebug() << "changed width" << _timestampWidth << _senderWidth << _contentsWidth;
-  //setImplicitHeight(implicitHeight() + 5);
-
-  if(colType == ContentsColumn) {
-    layout()->prepare();
-    setImplicitHeight(layout()->height());
-  }
-
+  Q_UNUSED(colType)
+  setImplicitWidth(_timestampWidth + _senderWidth + _contentsWidth);
   update();
 }
 
 /**************************************************************************************/
 
-QmlChatLine::ColumnLayout::ColumnLayout(const QmlChatLine *parent)
+QmlChatLine::Layout::Layout(QmlChatLine *parent)
   : _parent(parent)
 {
+  _timestampLayout = new TimestampLayout(parent);
+  _senderLayout = new SenderLayout(parent);
+  _contentsLayout = new ContentsLayout(parent);
+}
+
+QmlChatLine::Layout::~Layout() {
+  delete _timestampLayout;
+  delete _senderLayout;
+  delete _contentsLayout;
+}
+
+qreal QmlChatLine::Layout::height() const {
+  return _contentsLayout->height();
+}
+
+void QmlChatLine::Layout::compute() {
+  _timestampLayout->compute();
+  _senderLayout->compute();
+  _contentsLayout->compute();
+}
+
+void QmlChatLine::Layout::draw(QPainter *p) {
+  _timestampLayout->draw(p);
+  _senderLayout->draw(p);
+  _contentsLayout->draw(p);
+}
+
+/*************/
+
+QmlChatLine::ColumnLayout::ColumnLayout(QmlChatLine::ColumnType type, QmlChatLine *parent)
+  : _parent(parent),
+    _type(type),
+    _layout(0)
+{
+
+}
+
+QmlChatLine::ColumnLayout::~ColumnLayout() {
+  delete _layout;
+}
+
+void QmlChatLine::ColumnLayout::initLayout(QTextOption::WrapMode wrapMode, Qt::Alignment alignment) {
+  if(_layout)
+    delete _layout;
+
+  const RenderData::Column &data = chatLine()->renderData()[columnType()];
+  _layout = new QTextLayout(data.text);
+
+  QTextOption option;
+  option.setWrapMode(wrapMode);
+  option.setAlignment(alignment);
+  layout()->setTextOption(option);
 
+  QList<QTextLayout::FormatRange> formats = GraphicalUi::uiStyle()->toTextLayoutList(data.formats, layout()->text().length(), chatLine()->renderData().messageLabel);
+  layout()->setAdditionalFormats(formats);
+
+  compute();
 }
 
 qreal QmlChatLine::ColumnLayout::height() const {
-  return chatLine()->contentsWidth()/20;
+  return layout()->boundingRect().height();
 }
 
-void QmlChatLine::ColumnLayout::prepare() {
+void QmlChatLine::ColumnLayout::compute() {
+  qreal width = chatLine()->columnBoundingRect(columnType()).width();
+  qreal h = 0;
+  layout()->beginLayout();
+  forever {
+    QTextLine line = layout()->createLine();
+    if(!line.isValid())
+      break;
 
+    line.setLineWidth(width);
+    line.setPosition(QPointF(0, h));
+    h += line.height();
+  }
+  layout()->endLayout();
 }
 
 void QmlChatLine::ColumnLayout::draw(QPainter *p) {
-  p->drawText(chatLine()->boundingRect(), chatLine()->renderData()[ContentsColumn].text);
-  //p->drawText(chatLine()->timestampWidth() + chatLine()->columnSpacing(), 0, chatLine()->renderData()[SenderColumn].text);
-  //p->drawText(chatLine()->timestampWidth() + chatLine()->senderWidth() + 2*chatLine()->columnSpacing(), 0, chatLine()->renderData()[ContentsColumn].text);
+  p->save();
+
+  QRectF rect = chatLine()->columnBoundingRect(columnType());
+
+  qreal layoutWidth = layout()->minimumWidth();
+  qreal offset = 0;
+
+  if(layout()->textOption().alignment() == Qt::AlignRight) {
+    /*
+      if(chatScene()->senderCutoffMode() == ChatScene::CutoffLeft)
+        offset = qMin(width() - layoutWidth, (qreal)0);
+      else
+        offset = qMax(layoutWidth - width(), (qreal)0);
+    */
+      offset = qMax(layoutWidth - rect.width(), (qreal)0);
+  }
+
+  if(layoutWidth > rect.width()) {
+    // Draw a nice gradient for longer items
+
+    QLinearGradient gradient;
+    if(offset < 0) {
+      gradient.setStart(0, 0);
+      gradient.setFinalStop(12, 0);
+      gradient.setColorAt(0, Qt::transparent);
+      gradient.setColorAt(1, Qt::white);
+    } else {
+      gradient.setStart(rect.width()-12, 0);
+      gradient.setFinalStop(rect.width(), 0);
+      gradient.setColorAt(0, Qt::white);
+      gradient.setColorAt(1, Qt::transparent);
+    }
+
+    QImage img(layout()->boundingRect().toRect().size(), QImage::Format_ARGB32_Premultiplied);
+    //img.fill(Qt::transparent);
+    QPainter imgPainter(&img);
+    imgPainter.fillRect(img.rect(), gradient);
+    imgPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
+    layout()->draw(&imgPainter, QPointF(qMax(offset, (qreal)0), 0), selectionFormats());
+    imgPainter.end();
+    p->drawImage(rect.topLeft(), img);
+  } else {
+    layout()->draw(p, rect.topLeft(), selectionFormats(), rect);
+  }
+
+  p->restore();
+}
+
+QVector<QTextLayout::FormatRange> QmlChatLine::ColumnLayout::selectionFormats() const {
+  return QVector<QTextLayout::FormatRange>();
+}
+
+/**************/
+
+QmlChatLine::TimestampLayout::TimestampLayout(QmlChatLine *chatLine)
+  : ColumnLayout(TimestampColumn, chatLine)
+{
+  initLayout(QTextOption::NoWrap, Qt::AlignLeft);
+}
+
+
+/**************/
+
+QmlChatLine::SenderLayout::SenderLayout(QmlChatLine *chatLine)
+  : ColumnLayout(SenderColumn, chatLine)
+{
+  initLayout(QTextOption::NoWrap, Qt::AlignRight);
+}
+
+
+/**************/
+
+QmlChatLine::ContentsLayout::ContentsLayout(QmlChatLine *chatLine)
+  : ColumnLayout(ContentsColumn, chatLine)
+{
+  initLayout(QTextOption::WrapAtWordBoundaryOrAnywhere, Qt::AlignLeft);
+}
 
+void QmlChatLine::ContentsLayout::compute() {
+  ColumnLayout::compute();
+  chatLine()->setImplicitHeight(layout()->boundingRect().height());
 }
index 0dd090d..a20035d 100644 (file)
@@ -75,6 +75,10 @@ public:
   };
 
   class ColumnLayout;
+  class TimestampLayout;
+  class SenderLayout;
+  class ContentsLayout;
+  class Layout;
 
   QmlChatLine(QDeclarativeItem *parent = 0);
   virtual ~QmlChatLine();
@@ -86,7 +90,7 @@ public:
   inline RenderData renderData() const { return _data; }
   void setRenderData(const RenderData &data);
 
-  ColumnLayout *layout() const;
+  Layout *layout();
 
   inline qreal timestampWidth() const { return _timestampWidth; }
   void setTimestampWidth(qreal w);
@@ -136,7 +140,7 @@ private:
 
   QVariant _test;
 
-  mutable ColumnLayout *_layout;
+  mutable Layout *_layout;
 };
 
 QDataStream &operator<<(QDataStream &out, const QmlChatLine::RenderData &data);
@@ -144,19 +148,64 @@ QDataStream &operator>>(QDataStream &in, QmlChatLine::RenderData &data);
 
 Q_DECLARE_METATYPE(QmlChatLine::RenderData)
 
-class QmlChatLine::ColumnLayout {
+/** Layout classes */
+
+class QmlChatLine::Layout {
 public:
-  explicit ColumnLayout(const QmlChatLine *parent);
-  virtual ~ColumnLayout() {}
+  explicit Layout(QmlChatLine *parent);
+  ~Layout();
 
   inline const QmlChatLine *chatLine() const { return _parent; }
 
   qreal height() const;
-  virtual void prepare();
+  void compute();
+  void draw(QPainter *p);
+
+private:
+  QmlChatLine *_parent;
+  QmlChatLine::ColumnLayout *_timestampLayout, *_senderLayout, *_contentsLayout;
+};
+
+class QmlChatLine::ColumnLayout {
+public:
+  explicit ColumnLayout(QmlChatLine::ColumnType col, QmlChatLine *chatLine);
+  virtual ~ColumnLayout();
+
+  inline QmlChatLine *chatLine() const { return _parent; }
+  inline ColumnType columnType() const { return _type; }
+
+  virtual qreal height() const;
+  virtual void compute();
   virtual void draw(QPainter *p);
 
+protected:
+  inline QTextLayout *layout() const { return _layout; }
+  void initLayout(QTextOption::WrapMode wrapMode, Qt::Alignment alignment);
+  QVector<QTextLayout::FormatRange> selectionFormats() const;
+
 private:
-  const QmlChatLine *_parent;
+  QmlChatLine *_parent;
+  ColumnType _type;
+  QTextLayout *_layout;
+};
+
+class QmlChatLine::TimestampLayout : public QmlChatLine::ColumnLayout {
+public:
+  explicit TimestampLayout(QmlChatLine *chatLine);
+
+};
+
+class QmlChatLine::SenderLayout : public QmlChatLine::ColumnLayout {
+public:
+  explicit SenderLayout(QmlChatLine *chatLine);
+
+};
+
+class QmlChatLine::ContentsLayout : public QmlChatLine::ColumnLayout {
+public:
+  explicit ContentsLayout(QmlChatLine *chatLine);
+
+  void compute();
 };
 
 #endif