Correctly handle overlapping graphicsitems
[quassel.git] / src / qtui / chatscene.cpp
index 5072e38..d78ee1c 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 <QDrag>
 #include <QGraphicsSceneMouseEvent>
 #include <QMenu>
+#include <QMenuBar>
 #include <QPersistentModelIndex>
 
+#ifdef HAVE_KDE
+#  include <KMenuBar>
+#else
+#  include <QMenuBar>
+#endif
+
 #ifdef HAVE_WEBKIT
 #  include <QWebView>
 #endif
@@ -39,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"
@@ -56,6 +65,9 @@ ChatScene::ChatScene(QAbstractItemModel *model, const QString &idString, qreal w
     _sceneRect(0, 0, width, 0),
     _firstLineRow(-1),
     _viewportHeight(0),
+    _markerLine(new MarkerLineItem(width)),
+    _markerLineValid(false),
+    _markerLineVisible(false),
     _cutoffMode(CutoffRight),
     _selectingItem(0),
     _selectionStart(-1),
@@ -69,6 +81,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();
@@ -99,6 +114,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
@@ -130,9 +147,52 @@ ColumnHandleItem *ChatScene::secondColumnHandle() const {
   return _secondColHandle;
 }
 
+ChatLine *ChatScene::chatLine(MsgId msgId, bool matchExact) 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)
+    return *start;
+
+  if(matchExact)
+    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
+    return _lines.last();
+
+  if(start == _lines.begin()) // not (yet?) in our scene
+    return 0;
+
+  // return the next-lower line
+  return *(--start);
+}
+
 ChatItem *ChatScene::chatItemAt(const QPointF &scenePos) const {
-  QGraphicsItem *item = itemAt(scenePos);
-  return dynamic_cast<ChatItem *>(item);
+  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;
 }
 
 bool ChatScene::containsBuffer(const BufferId &id) const {
@@ -143,6 +203,35 @@ 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(msgId.isValid()) {
+    ChatLine *line = chatLine(msgId, false);
+    if(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);
+        return;
+      }
+    }
+  }
+  _markerLineValid = false;
+  _markerLine->setVisible(false);
+}
+
 void ChatScene::rowsInserted(const QModelIndex &index, int start, int end) {
   Q_UNUSED(index);
 
@@ -273,6 +362,14 @@ 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(isSingleBufferScene()) {
+    if(!_markerLineValid || !atBottom) {
+      MsgId msgId = Client::markerLine(singleBufferId());
+      setMarkerLine(msgId);
+    }
+  }
 }
 
 void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
@@ -366,6 +463,14 @@ void ChatScene::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e
   updateSceneRect();
 }
 
+void ChatScene::rowsRemoved() {
+  // move the marker line if necessary
+  if(isSingleBufferScene()) {
+    MsgId msgId = Client::markerLine(singleBufferId());
+    setMarkerLine(msgId);
+  }
+}
+
 void ChatScene::dataChanged(const QModelIndex &tl, const QModelIndex &br) {
   layout(tl.row(), br.row(), _sceneRect.width());
 }
@@ -492,6 +597,7 @@ void ChatScene::secondHandlePositionChanged(qreal xpos) {
 void ChatScene::setHandleXLimits() {
   _firstColHandle->setXLimits(0, _secondColHandle->sceneLeft());
   _secondColHandle->setXLimits(_firstColHandle->sceneRight(), width() - minContentsWidth);
+  update();
 }
 
 void ChatScene::setSelectingItem(ChatItem *item) {
@@ -598,6 +704,9 @@ void ChatScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
     // no item -> default scene actions
     GraphicalUi::contextMenuActionProvider()->addActions(&menu, filter(), BufferId());
 
+  if (QtUi::mainWindow()->menuBar()->isHidden())
+    menu.addAction(QtUi::actionCollection("General")->action("ToggleMenuBar"));
+
   menu.exec(event->screenPos());
 
 }
@@ -749,10 +858,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())
@@ -806,12 +915,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) {