Fix trimming
[quassel.git] / src / qtui / chatscene.cpp
index 3f0b18d..e5537a0 100644 (file)
@@ -46,6 +46,8 @@
 #include "columnhandleitem.h"
 #include "contextmenuactionprovider.h"
 #include "iconloader.h"
+#include "mainwin.h"
+#include "markerlineitem.h"
 #include "messagefilter.h"
 #include "qtui.h"
 #include "qtuistyle.h"
@@ -63,6 +65,10 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w
     _sceneRect(0, 0, width, 0),
     _firstLineRow(-1),
     _viewportHeight(0),
+    _markerLine(new MarkerLineItem(width)),
+    _markerLineVisible(false),
+    _markerLineValid(false),
+    _markerLineJumpPending(false),
     _cutoffMode(CutoffRight),
     _selectingItem(0),
     _selectionStart(-1),
@@ -76,6 +82,9 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w
     _singleBufferId = filter->singleBufferId();
   }
 
+  addItem(_markerLine);
+  connect(this, SIGNAL(sceneRectChanged(const QRectF &)), _markerLine, SLOT(sceneRectChanged(const QRectF &)));
+
   ChatViewSettings defaultSettings;
   int defaultFirstColHandlePos = defaultSettings.value("FirstColumnHandlePos", 80).toInt();
   int defaultSecondColHandlePos = defaultSettings.value("SecondColumnHandlePos", 200).toInt();
@@ -106,6 +115,8 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w
           this, SLOT(rowsInserted(const QModelIndex &, int, int)));
   connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
           this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
+  connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
+          this, SLOT(rowsRemoved()));
   connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(dataChanged(QModelIndex, QModelIndex)));
 
 #ifdef HAVE_WEBKIT
@@ -137,10 +148,67 @@ ColumnHandleItem *ChatScene::secondColumnHandle() const {
   return _secondColHandle;
 }
 
+ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact, bool ignoreDayChange) const {
+  if(!_lines.count())
+    return 0;
+
+  QList<ChatLine*>::ConstIterator start = _lines.begin();
+  QList<ChatLine*>::ConstIterator end = _lines.end();
+  QList<ChatLine*>::ConstIterator middle;
+
+  int n = int(end - start);
+  int half;
+
+  while(n > 0) {
+    half = n >> 1;
+    middle = start + half;
+    if((*middle)->msgId() < msgId) {
+      start = middle + 1;
+      n -= half + 1;
+    } else {
+      n = half;
+    }
+  }
+
+  if(start != end && (*start)->msgId() == msgId && (ignoreDayChange? (*start)->msgType() != Message::DayChange : true))
+    return *start;
+
+  if(matchExact)
+    return 0;
+
+  if(start == _lines.begin()) // not (yet?) in our scene
+    return 0;
+
+  // if we didn't find the exact msgId, take the next-lower one (this makes sense for lastSeen)
+
+  if(start == end) { // higher than last element
+    if(!ignoreDayChange)
+      return _lines.last();
+
+    for(int i = _lines.count() -1; i >= 0; i--) {
+      if(_lines.at(i)->msgType() != Message::DayChange)
+        return _lines.at(i);
+    }
+    return 0;
+  }
+
+  // return the next-lower line
+  if(!ignoreDayChange)
+    return *(--start);
+
+  do {
+    if((*(--start))->msgType() != Message::DayChange)
+      return *start;
+  } while(start != _lines.begin());
+  return 0;
+}
+
 ChatItem *ChatScene::chatItemAt(const QPointF &scenePos) const {
-  ChatLine *line = qgraphicsitem_cast<ChatLine*>(itemAt(scenePos));
-  if(line)
-    return line->itemAt(line->mapFromScene(scenePos));
+  foreach(QGraphicsItem *item, items(scenePos, Qt::IntersectsItemBoundingRect, Qt::AscendingOrder)) {
+    ChatLine *line = qgraphicsitem_cast<ChatLine*>(item);
+    if(line)
+      return line->itemAt(line->mapFromScene(scenePos));
+  }
   return 0;
 }
 
@@ -152,10 +220,74 @@ bool ChatScene::containsBuffer(const BufferId &id) const {
     return false;
 }
 
+void ChatScene::setMarkerLineVisible(bool visible) {
+  _markerLineVisible = visible;
+  if(visible && _markerLineValid)
+    markerLine()->setVisible(true);
+  else
+    markerLine()->setVisible(false);
+}
+
+void ChatScene::setMarkerLine(MsgId msgId) {
+  if(!isSingleBufferScene())
+    return;
+
+  if(!msgId.isValid())
+    msgId = Client::markerLine(singleBufferId());
+
+  if(msgId.isValid()) {
+    ChatLine *line = chatLine(msgId, false, true);
+    if(line) {
+      markerLine()->setChatLine(line);
+      // if this was the last line, we won't see it because it's outside the sceneRect
+      // .. which is exactly what we want :)
+      markerLine()->setPos(line->pos() + QPointF(0, line->height()));
+
+      // DayChange messages might have been hidden outside the scene rect, don't make the markerline visible then!
+      if(markerLine()->pos().y() >= sceneRect().y()) {
+        _markerLineValid = true;
+        if(_markerLineVisible)
+          markerLine()->setVisible(true);
+        if(_markerLineJumpPending) {
+          _markerLineJumpPending = false;
+          if(markerLine()->isVisible()) {
+            markerLine()->ensureVisible(QRectF(), 50, 50);
+          }
+        }
+        return;
+      }
+    }
+  }
+  _markerLineValid = false;
+  markerLine()->setVisible(false);
+}
+
+void ChatScene::jumpToMarkerLine(bool requestBacklog) {
+  if(!isSingleBufferScene())
+    return;
+
+  if(markerLine()->isVisible()) {
+    markerLine()->ensureVisible(QRectF(), 50, 50);
+    return;
+  }
+  if(!_markerLineValid && requestBacklog) {
+    MsgId msgId = Client::markerLine(singleBufferId());
+    if(msgId.isValid()) {
+      _markerLineJumpPending = true;
+      Client::backlogManager()->requestBacklog(singleBufferId(), msgId, -1, -1, 0);
+
+      // If we filtered out the lastSeenMsg (by changing filters after setting it), we'd never jump because the above request
+      // won't fetch any prior lines. Thus, trigger a dynamic backlog request just in case, so repeated
+      // jump tries will eventually cause enough backlog to be fetched.
+      // This is a bit hackish, but not wasteful, as jumping to the top of the ChatView would trigger a dynamic fetch anyway.
+      this->requestBacklog();
+    }
+  }
+}
+
 void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
   Q_UNUSED(index);
 
-
 //   QModelIndex sidx = model()->index(start, 2);
 //   QModelIndex eidx = model()->index(end, 2);
 //   qDebug() << "rowsInserted:";
@@ -243,6 +375,8 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
     for(int i = 0; i <= end; i++) {
       line = _lines.at(i);
       line->setPos(0, line->pos().y() - h);
+      if(line == markerLine()->chatLine())
+        markerLine()->setPos(line->pos() + QPointF(0, line->height()));
     }
   }
 
@@ -282,6 +416,10 @@ void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
   if(atBottom) {
     emit lastLineChanged(_lines.last(), h);
   }
+
+  // now move the marker line if necessary. we don't need to do anything if we appended lines though...
+  if(!_markerLineValid)
+    setMarkerLine();
 }
 
 void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
@@ -304,6 +442,8 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
   QList<ChatLine *>::iterator lineIter = _lines.begin() + start;
   int lineCount = start;
   while(lineIter != _lines.end() && lineCount <= end) {
+    if((*lineIter) == markerLine()->chatLine())
+      markerLine()->setChatLine(0);
     h += (*lineIter)->height();
     delete *lineIter;
     lineIter = _lines.erase(lineIter);
@@ -375,6 +515,11 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
   updateSceneRect();
 }
 
+void ChatScene::rowsRemoved() {
+  // move the marker line if necessary
+  setMarkerLine();
+}
+
 void ChatScene::dataChanged(const QModelIndex &tl, const QModelIndex &br) {
   layout(tl.row(), br.row(), _sceneRect.width());
 }
@@ -422,6 +567,7 @@ void ChatScene::layout(int start, int end, qreal width) {
 
   updateSceneRect(width);
   setHandleXLimits();
+  setMarkerLine();
   emit layoutChanged();
 
 //   clock_t endT = clock();
@@ -871,14 +1017,6 @@ void ChatScene::updateSceneRect(const QRectF &rect) {
   update();
 }
 
-bool ChatScene::event(QEvent *e) {
-  if(e->type() == QEvent::ApplicationPaletteChange) {
-    _firstColHandle->setColor(QApplication::palette().windowText().color());
-    _secondColHandle->setColor(QApplication::palette().windowText().color());
-  }
-  return QGraphicsScene::event(e);
-}
-
 // ========================================
 //  Webkit Only stuff
 // ========================================