ChatItems are no longer QGraphicsItems
authorManuel Nickschas <sputnick@quassel-irc.org>
Fri, 29 Jan 2010 13:46:56 +0000 (14:46 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Fri, 29 Jan 2010 13:56:20 +0000 (14:56 +0100)
This saves ~75% of graphicsitems in use, and should save some RAM and CPU.
It also solves some bugs with drawing, e.g. the marker line is now also
visible if the following line is highlighted.
This is the first step of ChatView optimizations I plan.

src/qtui/chatitem.cpp
src/qtui/chatitem.h
src/qtui/chatline.cpp
src/qtui/chatline.h
src/qtui/chatscene.cpp
src/qtui/chatscene.h
src/qtui/chatviewsearchcontroller.cpp

index 6add446..4855f0c 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 #include "qtui.h"
 #include "qtuistyle.h"
 
-ChatItem::ChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent)
-  : QGraphicsItem(parent),
-    _boundingRect(0, 0, width, height),
-    _selectionMode(NoSelection),
-    _selectionStart(-1)
+ChatItem::ChatItem(const QRectF &boundingRect, ChatLine *parent)
+: _parent(parent),
+  _boundingRect(boundingRect),
+  _selectionMode(NoSelection),
+  _selectionStart(-1)
 {
-  setAcceptHoverEvents(true);
-  setZValue(20);
-  setPos(pos);
-}
-
-const QAbstractItemModel *ChatItem::model() const {
-  return static_cast<ChatLine *>(parentItem())->model();
-}
 
-int ChatItem::row() const {
-  return static_cast<ChatLine *>(parentItem())->row();
 }
 
 QVariant ChatItem::data(int role) const {
@@ -67,7 +57,8 @@ QVariant ChatItem::data(int role) const {
   return model()->data(index, role);
 }
 
-qint16 ChatItem::posToCursor(const QPointF &pos) const {
+qint16 ChatItem::posToCursor(const QPointF &posInLine) const {
+  QPointF pos = mapFromLine(posInLine);
   if(pos.y() > height()) return data(MessageModel::DisplayRole).toString().length();
   if(pos.y() < 0) return 0;
 
@@ -112,8 +103,6 @@ void ChatItem::doLayout(QTextLayout *layout) const {
 }
 
 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);
@@ -131,7 +120,7 @@ void ChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
 
   QTextLayout layout;
   initLayout(&layout);
-  layout.draw(painter, QPointF(0,0), additionalFormats(), boundingRect());
+  layout.draw(painter, pos(), additionalFormats(), boundingRect());
 
   //  layout()->draw(painter, QPointF(0,0), formats, boundingRect());
 
@@ -238,27 +227,27 @@ void ChatItem::setSelection(SelectionMode mode, qint16 start, qint16 end) {
   _selectionMode = mode;
   _selectionStart = start;
   _selectionEnd = end;
-  update();
+  chatLine()->update();
 }
 
 void ChatItem::setFullSelection() {
   if(_selectionMode != FullSelection) {
     _selectionMode = FullSelection;
-    update();
+    chatLine()->update();
   }
 }
 
 void ChatItem::clearSelection() {
   if(_selectionMode != NoSelection) {
     _selectionMode = NoSelection;
-    update();
+    chatLine()->update();
   }
 }
 
 void ChatItem::continueSelecting(const QPointF &pos) {
   _selectionMode = PartialSelection;
   _selectionEnd = posToCursor(pos);
-  update();
+  chatLine()->update();
 }
 
 bool ChatItem::isPosOverSelection(const QPointF &pos) const {
@@ -305,18 +294,18 @@ void ChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clickMode) {
     chatScene()->setSelectingItem(this);
     _selectionStart = _selectionEnd = posToCursor(pos);
     _selectionMode = NoSelection; // will be set to PartialSelection by mouseMoveEvent
-    update();
+    chatLine()->update();
   }
 }
 
 void ChatItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
   if(event->buttons() == Qt::LeftButton) {
-    if(contains(event->pos())) {
+    if(boundingRect().contains(event->pos())) {
       qint16 end = posToCursor(event->pos());
       if(end != _selectionEnd) {
         _selectionEnd = end;
         _selectionMode = (_selectionStart != _selectionEnd ? PartialSelection : NoSelection);
-        update();
+        chatLine()->update();
       }
     } else {
       setFullSelection();
@@ -390,11 +379,11 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op
       gradient.setColorAt(0, Qt::white);
       gradient.setColorAt(1, Qt::black);
     }
-    maskPainter.fillRect(boundingRect(), gradient);
+    maskPainter.fillRect(0, 0, pixmap.width(), pixmap.height(), gradient);
     pixmap.setAlphaChannel(mask);
-    painter->drawPixmap(0, 0, pixmap);
+    painter->drawPixmap(pos(), pixmap);
   } else {
-    layout.draw(painter, QPointF(0,0), additionalFormats(), boundingRect());
+    layout.draw(painter, pos(), additionalFormats(), boundingRect());
   }
 }
 
@@ -404,10 +393,11 @@ void SenderChatItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *op
 
 ContentsChatItem::ActionProxy ContentsChatItem::_actionProxy;
 
-ContentsChatItem::ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent)
-  : ChatItem(0, 0, pos, parent),
+ContentsChatItem::ContentsChatItem(const QPointF &pos, const qreal &width, ChatLine *parent)
+  : ChatItem(QRectF(pos, QSizeF(width, 0)), parent),
     _data(0)
 {
+  setPos(pos);
   setGeometryByWidth(width);
 }
 
@@ -440,10 +430,9 @@ qreal ContentsChatItem::setGeometryByWidth(qreal w) {
   delete _data;
   _data = 0;
 
-  if(w != width() || h != height()) {
-    prepareGeometryChange();
+  if(w != width() || h != height())
     setGeometry(w, h);
-  }
+
   return h;
 }
 
@@ -517,11 +506,11 @@ QVector<QTextLayout::FormatRange> ContentsChatItem::additionalFormats() const {
 void ContentsChatItem::endHoverMode() {
   if(privateData()) {
     if(privateData()->currentClickable.isValid()) {
-      setCursor(Qt::ArrowCursor);
+      chatLine()->setCursor(Qt::ArrowCursor);
       privateData()->currentClickable = Clickable();
     }
     clearWebPreview();
-    update();
+    chatLine()->update();
   }
 }
 
@@ -551,7 +540,7 @@ void ContentsChatItem::handleClick(const QPointF &pos, ChatScene::ClickMode clic
       setSelectionStart(start);
       setSelectionEnd(end);
     }
-    update();
+    chatLine()->update();
   } else if(clickMode == ChatScene::TripleClick) {
     setSelection(PartialSelection, 0, data(ChatLineModel::DisplayRole).toString().length());
   }
@@ -584,9 +573,9 @@ void ContentsChatItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
         onClickable = true;
     }
     if(onClickable) {
-      setCursor(Qt::PointingHandCursor);
+      chatLine()->setCursor(Qt::PointingHandCursor);
       privateData()->currentClickable = click;
-      update();
+      chatLine()->update();
       return;
     }
   }
@@ -644,7 +633,7 @@ void ContentsChatItem::showWebPreview(const Clickable &click) {
   qreal height = line.height();
   qreal y = height * line.lineNumber();
 
-  QPointF topLeft = scenePos() + QPointF(x, y);
+  QPointF topLeft = mapToScene(pos()) + QPointF(x, y);
   QRectF urlRect = QRectF(topLeft.x(), topLeft.y(), width, height);
 
   QString urlstr = data(ChatLineModel::DisplayRole).toString().mid(click.start(), click.length());
index 36fb1c1..0e6a3de 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -22,7 +22,6 @@
 #define CHATITEM_H_
 
 #include <QAction>
-#include <QGraphicsItem>
 #include <QObject>
 
 #include "chatlinemodel.h"
 
 #include <QTextLayout>
 
-class ChatItem : public QGraphicsItem {
+class ChatLine;
+
+/* All external positions are relative to the parent ChatLine */
+/* Yes, that's also true for the boundingRect() and related things */
+
+class ChatItem {
 protected:
-  ChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent);
+  // boundingRect is relative to the parent ChatLine
+  ChatItem(const QRectF &boundingRect, ChatLine *parent);
+  virtual ~ChatItem() {}
 
 public:
-  const QAbstractItemModel *model() const;
-  int row() const;
+  inline const QAbstractItemModel *model() const;
+  inline ChatLine *chatLine() const;
+  inline ChatScene *chatScene() const;
+  inline int row() const;
   virtual ChatLineModel::ColumnType column() const = 0;
-  inline ChatScene *chatScene() const { return qobject_cast<ChatScene *>(scene()); }
 
-  inline QRectF boundingRect() const { return _boundingRect; }
-  inline qreal width() const { return _boundingRect.width(); }
-  inline qreal height() const { return _boundingRect.height(); }
+  // The boundingRect() is relative to the parent ChatLine
+  inline QRectF boundingRect() const;
+  inline qreal width() const;
+  inline qreal height() const;
+  inline QPointF pos() const;
+  inline qreal x() const;
+  inline qreal y() const;
+
+  inline QPointF mapToLine(const QPointF &) const;
+  inline QPointF mapFromLine(const QPointF &) const;
+  inline QPointF mapToScene(const QPointF &) const;
+  inline QPointF mapFromScene(const QPointF &) const;
 
   void initLayoutHelper(QTextLayout *layout, QTextOption::WrapMode, Qt::Alignment = Qt::AlignLeft) const;
   virtual inline void initLayout(QTextLayout *layout) const {
@@ -56,8 +72,7 @@ public:
   virtual UiStyle::FormatList formatList() const;
 
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
-  enum { Type = ChatScene::ChatItemType };
-  virtual inline int type() const { return Type; }
+  virtual inline int type() const { return ChatScene::ChatItemType; }
 
   QVariant data(int role) const;
 
@@ -84,6 +99,9 @@ protected:
   virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
   virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
   virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
+  virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *) {};
+  virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *) {};
+  virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *) {};
 
   void paintBackground(QPainter *);
   QVector<QTextLayout::FormatRange> selectionFormats() const;
@@ -100,29 +118,21 @@ protected:
 
   qint16 posToCursor(const QPointF &pos) const;
 
-  // WARNING: setGeometry and setHeight should not be used without either:
-  //  a) calling prepareGeometryChange() immediately before setColumns()
-  //  b) calling Chatline::setPos() immediately afterwards
-  inline void setGeometry(qreal width, qreal height) {
-    _boundingRect.setWidth(width);
-    _boundingRect.setHeight(height);
-  }
-  inline void setHeight(const qreal &height) {
-    _boundingRect.setHeight(height);
-  }
-  inline void setWidth(const qreal &width) {
-    _boundingRect.setWidth(width);
-  }
+  inline void setGeometry(qreal width, qreal height) { _boundingRect.setSize(QSizeF(width, height)); }
+  inline void setHeight(const qreal &height) { _boundingRect.setHeight(height); }
+  inline void setWidth(const qreal &width) { _boundingRect.setWidth(width); }
+  inline void setPos(const QPointF &pos) { _boundingRect.moveTopLeft(pos); }
 
 private:
-  // internal selection stuff
-  void setSelection(int start, int length);
-
+  ChatLine *_parent;
   QRectF _boundingRect;
 
   SelectionMode _selectionMode;
   qint16 _selectionStart, _selectionEnd;
 
+  // internal selection stuff
+  void setSelection(int start, int length);
+
   friend class ChatLine;
 };
 
@@ -133,9 +143,8 @@ private:
 //! A ChatItem for the timestamp column
 class TimestampChatItem : public ChatItem {
 public:
-  TimestampChatItem(const qreal &width, const qreal &height, QGraphicsItem *parent) : ChatItem(width, height, QPointF(0, 0), parent) {}
-  enum { Type = ChatScene::TimestampChatItemType };
-  virtual inline int type() const { return Type; }
+  TimestampChatItem(const QRectF &boundingRect, ChatLine *parent) : ChatItem(boundingRect, parent) {}
+  virtual inline int type() const { return ChatScene::TimestampChatItemType; }
   virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::TimestampColumn; }
 };
 
@@ -145,13 +154,12 @@ public:
 //! A ChatItem for the sender column
 class SenderChatItem : public ChatItem {
 public:
-  SenderChatItem(const qreal &width, const qreal &height, const QPointF &pos, QGraphicsItem *parent) : ChatItem(width, height, pos, parent) {}
+  SenderChatItem(const QRectF &boundingRect, ChatLine *parent) : ChatItem(boundingRect, parent) {}
   virtual inline ChatLineModel::ColumnType column() const { return ChatLineModel::SenderColumn; }
 
 protected:
   virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
-  enum { Type = ChatScene::SenderChatItemType };
-  virtual inline int type() const { return Type; }
+  virtual inline int type() const { return ChatScene::SenderChatItemType; }
   virtual inline void initLayout(QTextLayout *layout) const {
     initLayoutHelper(layout, QTextOption::ManualWrap, Qt::AlignRight);
     doLayout(layout);
@@ -168,11 +176,10 @@ class ContentsChatItem : public ChatItem {
   Q_DECLARE_TR_FUNCTIONS(ContentsChatItem)
 
 public:
-  ContentsChatItem(const qreal &width, const QPointF &pos, QGraphicsItem *parent);
+  ContentsChatItem(const QPointF &pos, const qreal &width, ChatLine *parent);
   ~ContentsChatItem();
 
-  enum { Type = ChatScene::ContentsChatItemType };
-  virtual inline int type() const { return Type; }
+  virtual inline int type() const { return ChatScene::ContentsChatItemType; }
 
   inline ChatLineModel::ColumnType column() const { return ChatLineModel::ContentsColumn; }
   QFontMetricsF *fontMetrics() const;
@@ -268,4 +275,26 @@ private:
 
 /*************************************************************************************************/
 
+#include "chatline.h"  /* avoid circular includes */
+
+// Inlines
+
+ChatLine *ChatItem::chatLine() const { return _parent; }
+ChatScene *ChatItem::chatScene() const { return chatLine()->chatScene(); }
+const QAbstractItemModel *ChatItem::model() const { return chatLine()->model(); }
+int ChatItem::row() const { return chatLine()->row(); }
+
+QRectF ChatItem::boundingRect() const { return _boundingRect; }
+qreal ChatItem::width() const { return _boundingRect.width(); }
+qreal ChatItem::height() const { return _boundingRect.height(); }
+QPointF ChatItem::pos() const { return _boundingRect.topLeft(); }
+qreal ChatItem::x() const { return pos().x(); }
+qreal ChatItem::y() const { return pos().y(); }
+
+QPointF ChatItem::mapToLine(const QPointF &p) const { return p + pos(); }
+QPointF ChatItem::mapFromLine(const QPointF &p) const { return p - pos(); }
+// relative to the ChatLine
+QPointF ChatItem::mapToScene(const QPointF &p) const { return chatLine()->mapToScene(p /* + pos() */); }
+QPointF ChatItem::mapFromScene(const QPointF &p) const { return chatLine()->mapFromScene(p) /* - pos() */; }
+
 #endif
index 38e78c6..6032cbe 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -27,6 +27,7 @@
 #include "client.h"
 #include "chatitem.h"
 #include "chatline.h"
+#include "chatview.h"
 #include "columnhandleitem.h"
 #include "messagemodel.h"
 #include "networkmodel.h"
 #include "qtuistyle.h"
 
 ChatLine::ChatLine(int row, QAbstractItemModel *model,
-                  const qreal &width,
-                  const qreal &timestampWidth, const qreal &senderWidth, const qreal &contentsWidth,
-                  const QPointF &senderPos, const QPointF &contentsPos,
-                  QGraphicsItem *parent)
+                   const qreal &width,
+                   const qreal &timestampWidth, const qreal &senderWidth, const qreal &contentsWidth,
+                   const QPointF &senderPos, const QPointF &contentsPos,
+                   QGraphicsItem *parent)
   : QGraphicsItem(parent),
     _row(row), // needs to be set before the items
     _model(model),
-    _contentsItem(contentsWidth, contentsPos, this),
-    _senderItem(senderWidth, _contentsItem.height(), senderPos, this),
-    _timestampItem(timestampWidth, _contentsItem.height(), this),
+    _contentsItem(contentsPos, contentsWidth, this),
+    _senderItem(QRectF(senderPos, QSizeF(senderWidth, _contentsItem.height())), this),
+    _timestampItem(QRectF(0, 0, timestampWidth, _contentsItem.height()), this),
     _width(width),
     _height(_contentsItem.height()),
-    _selection(0)
+    _selection(0),
+    _mouseGrabberItem(0)
 {
   Q_ASSERT(model);
   QModelIndex index = model->index(row, ChatLineModel::ContentsColumn);
   setZValue(0);
-  setHighlighted(model->data(index, MessageModel::FlagsRole).toInt() & Message::Highlight);
+  setAcceptHoverEvents(true);
+  setHighlighted(index.data(MessageModel::FlagsRole).toInt() & Message::Highlight);
 }
 
-ChatItem &ChatLine::item(ChatLineModel::ColumnType column) {
+ChatItem *ChatLine::item(ChatLineModel::ColumnType column) {
   switch(column) {
     case ChatLineModel::TimestampColumn:
-      return _timestampItem;
+      return &_timestampItem;
     case ChatLineModel::SenderColumn:
-      return _senderItem;
+      return &_senderItem;
     case ChatLineModel::ContentsColumn:
-      return _contentsItem;
+      return &_contentsItem;
   default:
-    return *(ChatItem *)0; // provoke an error
+    return 0;
   }
 }
 
-// NOTE: senderPos is in ChatLines coordinate system!
+ChatItem *ChatLine::itemAt(const QPointF &pos) {
+  if(_contentsItem.boundingRect().contains(pos))
+    return &_contentsItem;
+  if(_senderItem.boundingRect().contains(pos))
+    return &_senderItem;
+  if(_timestampItem.boundingRect().contains(pos))
+    return &_timestampItem;
+  return 0;
+}
+
+void ChatLine::setMouseGrabberItem(ChatItem *item) {
+  _mouseGrabberItem = item;
+}
+
+bool ChatLine::sceneEvent(QEvent *event) {
+  if(event->type() == QEvent::GrabMouse) {
+    // get mouse cursor pos relative to us
+    ChatView *view = chatScene()->chatView();
+    QPointF linePos = mapFromScene(view->mapToScene(view->mapFromGlobal(QCursor::pos())));
+    setMouseGrabberItem(itemAt(linePos));
+  } else if(event->type() == QEvent::UngrabMouse) {
+    setMouseGrabberItem(0);
+  }
+  return QGraphicsItem::sceneEvent(event);
+}
+
 void ChatLine::setFirstColumn(const qreal &timestampWidth, const qreal &senderWidth, const QPointF &senderPos) {
-  _timestampItem.prepareGeometryChange();
   _timestampItem.setGeometry(timestampWidth, _height);
-  // senderItem doesn't need a geom change as it's Pos is changed (ensured by void ChatScene::firstHandlePositionChanged(qreal xpos))
   _senderItem.setGeometry(senderWidth, _height);
   _senderItem.setPos(senderPos);
 }
 
-// NOTE: contentsPos is in ChatLines coordinate system!
-void ChatLine::setSecondColumn(const qreal &senderWidth, const qreal &contentsWidth,
-                              const QPointF &contentsPos, qreal &linePos) {
-  // contentsItem doesn't need a geom change as it's Pos is changed (ensured by void ChatScene::firstHandlePositionChanged(qreal xpos))
+void ChatLine::setSecondColumn(const qreal &senderWidth, const qreal &contentsWidth, const QPointF &contentsPos, qreal &linePos) {
+  // linepos is the *bottom* position for the line
   qreal height = _contentsItem.setGeometryByWidth(contentsWidth);
   linePos -= height;
-  bool needGeometryChange = linePos == pos().y();
+  bool needGeometryChange = (height != _height);
 
-  if(needGeometryChange) {
-    _timestampItem.prepareGeometryChange();
-    _senderItem.prepareGeometryChange();
-  }
   _timestampItem.setHeight(height);
   _senderItem.setGeometry(senderWidth, height);
-
   _contentsItem.setPos(contentsPos);
 
   if(needGeometryChange)
@@ -103,14 +122,13 @@ void ChatLine::setSecondColumn(const qreal &senderWidth, const qreal &contentsWi
 }
 
 void ChatLine::setGeometryByWidth(const qreal &width, const qreal &contentsWidth, qreal &linePos) {
+  // linepos is the *bottom* position for the line
   qreal height = _contentsItem.setGeometryByWidth(contentsWidth);
   linePos -= height;
   bool needGeometryChange = (height != _height || width != _width);
 
   if(height != _height) {
-    _timestampItem.prepareGeometryChange();
     _timestampItem.setHeight(height);
-    _senderItem.prepareGeometryChange();
     _senderItem.setHeight(height);
   }
 
@@ -129,9 +147,9 @@ void ChatLine::setSelected(bool selected, ChatLineModel::ColumnType minColumn) {
     if(sel != _selection) {
       _selection = sel;
       for(int i = 0; i < minColumn; i++)
-       item((ChatLineModel::ColumnType)i).clearSelection();
+        item((ChatLineModel::ColumnType)i)->clearSelection();
       for(int i = minColumn; i <= ChatLineModel::ContentsColumn; i++)
-       item((ChatLineModel::ColumnType)i).setFullSelection();
+        item((ChatLineModel::ColumnType)i)->setFullSelection();
       update();
     }
   } else {
@@ -139,7 +157,7 @@ void ChatLine::setSelected(bool selected, ChatLineModel::ColumnType minColumn) {
     if(sel != _selection) {
       _selection = sel;
       for(int i = 0; i <= ChatLineModel::ContentsColumn; i++)
-       item((ChatLineModel::ColumnType)i).clearSelection();
+        item((ChatLineModel::ColumnType)i)->clearSelection();
       update();
     }
   }
@@ -168,12 +186,18 @@ void ChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
   if(_selection & Selected) {
     QTextCharFormat selFmt = QtUi::style()->format(UiStyle::formatType(type), label | UiStyle::Selected);
     if(selFmt.hasProperty(QTextFormat::BackgroundBrush)) {
-      qreal left = item((ChatLineModel::ColumnType)(_selection & ItemMask)).x();
+      qreal left = item((ChatLineModel::ColumnType)(_selection & ItemMask))->pos().x();
       QRectF selectRect(left, 0, width() - left, height());
       painter->fillRect(selectRect, selFmt.background());
     }
   }
 
+  // draw chatitems
+  // the items draw themselves at the correct position
+  timestampItem()->paint(painter, option, widget);
+  senderItem()->paint(painter, option, widget);
+  contentsItem()->paint(painter, option, widget);
+
   // new line marker
   if(model_ && row() > 0  && chatScene()->isSingleBufferScene()) {
     QModelIndex prevRowIdx = model_->index(row() - 1, 0);
@@ -186,7 +210,7 @@ void ChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
       BufferId bufferId = BufferId(chatScene()->idString().toInt());
       MsgId lastSeenMsgId = Client::networkModel()->markerLineMsgId(bufferId);
       if(lastSeenMsgId < myMsgId && lastSeenMsgId >= prevMsgId) {
-        QLinearGradient gradient(0, 0, 0, contentsItem().fontMetrics()->lineSpacing());
+        QLinearGradient gradient(0, 0, 0, contentsItem()->fontMetrics()->lineSpacing());
         gradient.setColorAt(0, QtUi::style()->brush(UiStyle::MarkerLine).color()); // FIXME: Use full (gradient?) brush instead of just the color
         gradient.setColorAt(0.1, Qt::transparent);
         painter->fillRect(boundingRect(), gradient);
@@ -194,3 +218,47 @@ void ChatLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
     }
   }
 }
+
+// We need to dispatch all mouse-related events to the appropriate (mouse grabbing) ChatItem
+
+ChatItem *ChatLine::mouseEventTargetItem(const QPointF &pos) {
+  if(mouseGrabberItem())
+    return mouseGrabberItem();
+  return itemAt(pos);
+}
+
+void ChatLine::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
+  ChatItem *item = mouseEventTargetItem(event->pos());
+  if(item)
+    item->mouseMoveEvent(event);
+}
+
+void ChatLine::mousePressEvent(QGraphicsSceneMouseEvent *event) {
+  ChatItem *item = mouseEventTargetItem(event->pos());
+  if(item)
+    item->mousePressEvent(event);
+}
+
+void ChatLine::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
+  ChatItem *item = mouseEventTargetItem(event->pos());
+  if(item)
+    item->mouseReleaseEvent(event);
+}
+
+void ChatLine::hoverEnterEvent(QGraphicsSceneHoverEvent *event) {
+  ChatItem *item = mouseEventTargetItem(event->pos());
+  if(item)
+    item->hoverEnterEvent(event);
+}
+
+void ChatLine::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
+  ChatItem *item = mouseEventTargetItem(event->pos());
+  if(item)
+    item->hoverLeaveEvent(event);
+}
+
+void ChatLine::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
+  ChatItem *item = mouseEventTargetItem(event->pos());
+  if(item)
+    item->hoverMoveEvent(event);
+}
index 6fc21c7..24947d0 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 class ChatLine : public QGraphicsItem {
 public:
   ChatLine(int row, QAbstractItemModel *model,
-          const qreal &width,
-          const qreal &timestampWidth, const qreal &senderWidth, const qreal &contentsWidth,
-          const QPointF &senderPos, const QPointF &contentsPos,
-          QGraphicsItem *parent = 0);
+           const qreal &width,
+           const qreal &timestampWidth, const qreal &senderWidth, const qreal &contentsWidth,
+           const QPointF &senderPos, const QPointF &contentsPos,
+           QGraphicsItem *parent = 0);
 
   virtual inline QRectF boundingRect () const { return QRectF(0, 0, _width, _height); }
 
@@ -46,15 +46,17 @@ public:
   inline qreal width() const { return _width; }
   inline qreal height() const { return _height; }
 
-  ChatItem &item(ChatLineModel::ColumnType);
-  inline ChatItem &timestampItem() { return _timestampItem; }
-  inline ChatItem &senderItem() { return _senderItem; }
-  inline ContentsChatItem &contentsItem() { return _contentsItem; }
+  ChatItem *item(ChatLineModel::ColumnType);
+  ChatItem *itemAt(const QPointF &pos);
+  inline ChatItem *timestampItem() { return &_timestampItem; }
+  inline ChatItem *senderItem() { return &_senderItem; }
+  inline ContentsChatItem *contentsItem() { return &_contentsItem; }
 
   virtual void paint (QPainter * painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
   enum { Type = ChatScene::ChatLineType };
   virtual inline int type() const { return Type; }
 
+  // pos is relative to the parent ChatLine
   void setFirstColumn(const qreal &timestampWidth, const qreal &senderWidth, const QPointF &senderPos);
   // setSecondColumn and setGeometryByWidth both also relocate the chatline.
   // the _bottom_ position is passed via linePos. linePos is updated to the top of the chatLine.
@@ -64,6 +66,22 @@ public:
   void setSelected(bool selected, ChatLineModel::ColumnType minColumn = ChatLineModel::ContentsColumn);
   void setHighlighted(bool highlighted);
 
+protected:
+  virtual bool sceneEvent(QEvent *event);
+
+  // These need to be relayed to the appropriate ChatItem
+  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
+  virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
+  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
+  virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
+  virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
+  virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event);
+
+  ChatItem *mouseEventTargetItem(const QPointF &pos);
+
+  inline ChatItem *mouseGrabberItem() const { return _mouseGrabberItem; }
+  void setMouseGrabberItem(ChatItem *item);
+
 private:
   int _row;
   QAbstractItemModel *_model;
@@ -73,14 +91,16 @@ private:
   qreal _width, _height;
 
   enum { ItemMask = 0x3f,
-        Selected = 0x40,
-        Highlighted = 0x80
+         Selected = 0x40,
+         Highlighted = 0x80
   };
   // _selection[1..0] ... Min Selected Column (See MessageModel::ColumnType)
   // _selection[5..2] ... reserved for new column types
   // _selection[6] ...... Selected
   // _selection[7] ...... Highlighted
   quint8 _selection;  // save space, so we put both the col and the flags into one byte
+
+  ChatItem *_mouseGrabberItem;
 };
 
 #endif
index b748e1a..ed90582 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -138,8 +138,10 @@ ColumnHandleItem *ChatScene::secondColumnHandle() const {
 }
 
 ChatItem *ChatScene::chatItemAt(const QPointF &scenePos) const {
-  QGraphicsItem *item = itemAt(scenePos);
-  return dynamic_cast<ChatItem *>(item);
+  ChatLine *line = qgraphicsitem_cast<ChatLine*>(itemAt(scenePos));
+  if(line)
+    return line->itemAt(line->mapFromScene(scenePos));
+  return 0;
 }
 
 bool ChatScene::containsBuffer(const BufferId &id) const {
@@ -759,10 +761,10 @@ QString ChatScene::selection() const {
     QString result;
     for(int l = start; l <= end; l++) {
       if(_selectionMinCol == ChatLineModel::TimestampColumn)
-        result += _lines[l]->item(ChatLineModel::TimestampColumn).data(MessageModel::DisplayRole).toString() + " ";
+        result += _lines[l]->item(ChatLineModel::TimestampColumn)->data(MessageModel::DisplayRole).toString() + " ";
       if(_selectionMinCol <= ChatLineModel::SenderColumn)
-        result += _lines[l]->item(ChatLineModel::SenderColumn).data(MessageModel::DisplayRole).toString() + " ";
-      result += _lines[l]->item(ChatLineModel::ContentsColumn).data(MessageModel::DisplayRole).toString() + "\n";
+        result += _lines[l]->item(ChatLineModel::SenderColumn)->data(MessageModel::DisplayRole).toString() + " ";
+      result += _lines[l]->item(ChatLineModel::ContentsColumn)->data(MessageModel::DisplayRole).toString() + "\n";
     }
     return result;
   } else if(selectingItem())
@@ -816,12 +818,15 @@ ChatLineModel::ColumnType ChatScene::columnByScenePos(qreal x) const {
 }
 
 int ChatScene::rowByScenePos(qreal y) const {
-  // This is somewhat hacky... we look at the contents item that is at the given y position, since
-  // it has the full height. From this item, we can then determine the row index and hence the ChatLine.
-  // ChatItems cover their ChatLine, so we won't get to the latter directly.
-  ChatItem *contentItem = static_cast<ChatItem *>(itemAt(QPointF(_secondColHandle->sceneRight() + 1, y)));
-  if(!contentItem) return -1;
-  return contentItem->row();
+  QList<QGraphicsItem*> itemList = items(QPointF(0, y));
+
+  // ChatLine should be at the bottom of the list
+  for(int i = itemList.count()-1; i >= 0; i--) {
+    ChatLine *line = qgraphicsitem_cast<ChatLine *>(itemList.at(i));
+    if(line)
+      return line->row();
+  }
+  return -1;
 }
 
 void ChatScene::updateSceneRect(qreal width) {
index 7bf5be3..f3d6273 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
index add7478..671ac72 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -134,40 +134,40 @@ void ChatViewSearchController::updateHighlights(bool reuse) {
 
     if(!_highlightItems.isEmpty()) {
       if(!oldHighlightPos.isNull()) {
-       int start = 0; int end = _highlightItems.count() - 1;
-       QPointF startPos;
-       QPointF endPos;
-       while(1) {
-         startPos = _highlightItems[start]->scenePos();
-         endPos = _highlightItems[end]->scenePos();
-         if(startPos == oldHighlightPos) {
-           _currentHighlight = start;
-           break;
-         }
-         if(endPos == oldHighlightPos) {
-           _currentHighlight = end;
-           break;
-         }
-         if(end - start == 1) {
-           _currentHighlight = start;
-           break;
-         }
-         int pivot = (end + start) / 2;
-         QPointF pivotPos = _highlightItems[pivot]->scenePos();
-         if(startPos.y() == endPos.y()) {
-           if(oldHighlightPos.x() <= pivotPos.x())
-             end = pivot;
-           else
-             start = pivot;
-         } else {
-           if(oldHighlightPos.y() <= pivotPos.y())
-             end = pivot;
-           else
-             start = pivot;
-         }
-       }
+        int start = 0; int end = _highlightItems.count() - 1;
+        QPointF startPos;
+        QPointF endPos;
+        while(1) {
+          startPos = _highlightItems[start]->scenePos();
+          endPos = _highlightItems[end]->scenePos();
+          if(startPos == oldHighlightPos) {
+            _currentHighlight = start;
+            break;
+          }
+          if(endPos == oldHighlightPos) {
+            _currentHighlight = end;
+            break;
+          }
+          if(end - start == 1) {
+            _currentHighlight = start;
+            break;
+          }
+          int pivot = (end + start) / 2;
+          QPointF pivotPos = _highlightItems[pivot]->scenePos();
+          if(startPos.y() == endPos.y()) {
+            if(oldHighlightPos.x() <= pivotPos.x())
+              end = pivot;
+            else
+              start = pivot;
+          } else {
+            if(oldHighlightPos.y() <= pivotPos.y())
+              end = pivot;
+            else
+              start = pivot;
+          }
+        }
       } else {
-       _currentHighlight = _highlightItems.count() - 1;
+        _currentHighlight = _highlightItems.count() - 1;
       }
       _highlightItems[_currentHighlight]->setHighlighted(true);
       emit newCurrentHighlight(_highlightItems[_currentHighlight]);
@@ -190,7 +190,7 @@ void ChatViewSearchController::checkMessagesForHighlight(int start, int end) {
     if(_searchOnlyRegularMsgs) {
       index = model->index(row, 0);
       if(!checkType((Message::Type)index.data(MessageModel::TypeRole).toInt()))
-       continue;
+        continue;
     }
     highlightLine(_scene->chatLine(row));
   }
@@ -199,10 +199,10 @@ void ChatViewSearchController::checkMessagesForHighlight(int start, int end) {
 void ChatViewSearchController::updateHighlights(ChatLine *line) {
   QList<ChatItem *> checkItems;
   if(_searchSenders)
-    checkItems << &(line->item(MessageModel::SenderColumn));
+    checkItems << line->item(MessageModel::SenderColumn);
 
   if(_searchMsgs)
-    checkItems << &(line->item(MessageModel::ContentsColumn));
+    checkItems << line->item(MessageModel::ContentsColumn);
 
   QHash<quint64, QHash<quint64, QRectF> > wordRects;
   foreach(ChatItem *item, checkItems) {
@@ -220,7 +220,7 @@ void ChatViewSearchController::updateHighlights(ChatLine *line) {
       deleteAll = true;
   }
 
-  
+
   foreach(QGraphicsItem *child, line->childItems()) {
     SearchHighlightItem *highlightItem = qgraphicsitem_cast<SearchHighlightItem *>(child);
     if(!highlightItem)
@@ -232,9 +232,9 @@ void ChatViewSearchController::updateHighlights(ChatLine *line) {
     } else {
       int pos = _highlightItems.indexOf(highlightItem);
       if(pos == _currentHighlight) {
-       highlightPrev();
+        highlightPrev();
       } else if (pos < _currentHighlight) {
-       _currentHighlight--;
+        _currentHighlight--;
       }
 
       _highlightItems.removeAt(pos);
@@ -246,10 +246,10 @@ void ChatViewSearchController::updateHighlights(ChatLine *line) {
 void ChatViewSearchController::highlightLine(ChatLine *line) {
   QList<ChatItem *> checkItems;
   if(_searchSenders)
-    checkItems << &(line->item(MessageModel::SenderColumn));
+    checkItems << line->item(MessageModel::SenderColumn);
 
   if(_searchMsgs)
-    checkItems << &(line->item(MessageModel::ContentsColumn));
+    checkItems << line->item(MessageModel::ContentsColumn);
 
   foreach(ChatItem *item, checkItems) {
     foreach(QRectF wordRect, item->findWords(searchString(), caseSensitive())) {
@@ -284,13 +284,13 @@ void ChatViewSearchController::repositionHighlights(ChatLine *line) {
 
   QList<QPointF> wordPos;
   if(_searchSenders) {
-    foreach(QRectF wordRect, line->senderItem().findWords(searchString(), caseSensitive())) {
-      wordPos << QPointF(wordRect.x() + line->senderItem().x(), wordRect.y());
+    foreach(QRectF wordRect, line->senderItem()->findWords(searchString(), caseSensitive())) {
+      wordPos << QPointF(wordRect.x() + line->senderItem()->x(), wordRect.y());
     }
   }
   if(_searchMsgs) {
-    foreach(QRectF wordRect, line->contentsItem().findWords(searchString(), caseSensitive())) {
-      wordPos << QPointF(wordRect.x() + line->contentsItem().x(), wordRect.y());
+    foreach(QRectF wordRect, line->contentsItem()->findWords(searchString(), caseSensitive())) {
+      wordPos << QPointF(wordRect.x() + line->contentsItem()->x(), wordRect.y());
     }
   }
 
@@ -361,7 +361,7 @@ SearchHighlightItem::SearchHighlightItem(QRectF wordRect, QGraphicsItem *parent)
   : QObject(),
     QGraphicsItem(parent),
     _highlighted(false),
-    _alpha(100),
+    _alpha(70),
     _timeLine(150)
 {
   setPos(wordRect.x(), wordRect.y());
@@ -385,7 +385,7 @@ void SearchHighlightItem::setHighlighted(bool highlighted) {
 }
 
 void SearchHighlightItem::updateHighlight(qreal value) {
-  _alpha = 100 + (int)(155 * value);
+  _alpha = 70 + (int)(80 * value);
   update();
 }
 
@@ -393,7 +393,7 @@ void SearchHighlightItem::paint(QPainter *painter, const QStyleOptionGraphicsIte
   Q_UNUSED(option);
   Q_UNUSED(widget);
 
-  painter->setPen(QPen(QColor(0, 0, 0, _alpha), 1.5));
+  painter->setPen(QPen(QColor(0, 0, 0), 1.5));
   painter->setBrush(QColor(254, 237, 45, _alpha));
   painter->setRenderHints(QPainter::Antialiasing);
   qreal radius = boundingRect().height() * 0.30;