changeset 400:07eaf4e6003a

Merge from branch "item_appearance_adjustments"
author Chris Cannam
date Wed, 25 May 2011 14:59:09 +0100
parents 5cc0d897eb26 (current diff) b1f0fa991c49 (diff)
children c3276f8998ee
files src/dateitem.cpp src/dateitem.h
diffstat 20 files changed, 457 insertions(+), 209 deletions(-) [+]
line wrap: on
line diff
--- a/easyhg.pro	Tue May 24 13:29:27 2011 +0100
+++ b/easyhg.pro	Wed May 25 14:59:09 2011 +0100
@@ -41,7 +41,6 @@
     src/panned.h \
     src/connectionitem.h \
     src/textabbrev.h \
-    src/dateitem.h \
     src/colourset.h \
     src/debug.h \
     src/recentfiles.h \
@@ -55,6 +54,7 @@
     src/hgaction.h \
     src/historywidget.h \
     src/changesetscene.h \
+    src/changesetview.h \
     src/incomingdialog.h \
     src/uncommitteditem.h \
     src/settingsdialog.h \
@@ -77,7 +77,6 @@
     src/panned.cpp \
     src/connectionitem.cpp \
     src/textabbrev.cpp \
-    src/dateitem.cpp \
     src/colourset.cpp \
     src/debug.cpp \
     src/recentfiles.cpp \
@@ -90,6 +89,7 @@
     src/confirmcommentdialog.cpp \
     src/historywidget.cpp \
     src/changesetscene.cpp \
+    src/changesetview.cpp \
     src/incomingdialog.cpp \
     src/uncommitteditem.cpp \
     src/settingsdialog.cpp \
--- a/easyhg.qrc	Tue May 24 13:29:27 2011 +0100
+++ b/easyhg.qrc	Wed May 25 14:59:09 2011 +0100
@@ -22,6 +22,7 @@
         <file>images/hdd_unmount.png</file>
         <file>images/hdd_unmount-64.png</file>
         <file>images/fileopen.png</file>
+        <file>images/star.png</file>
         <file>images/easyhg-icon.png</file>
 	<file>easyhg.py</file>
 	<file>easyhg_en.qm</file>
Binary file images/star.png has changed
--- a/src/changesetdetailitem.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/changesetdetailitem.cpp	Wed May 25 14:59:09 2011 +0100
@@ -86,7 +86,7 @@
 
     QRectF r(0.5, 0.5, width - 1, height - 1);
     paint->setBrush(Qt::white);
-    paint->drawRect(r);
+    paint->drawRoundedRect(r, 10, 10);
 
     if (scale < 0.1) {
 	paint->restore();
--- a/src/changesetitem.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/changesetitem.cpp	Wed May 25 14:59:09 2011 +0100
@@ -33,6 +33,8 @@
 #include <QApplication>
 #include <QClipboard>
 
+QImage *ChangesetItem::m_star = 0;
+
 ChangesetItem::ChangesetItem(Changeset *cs) :
     m_changeset(cs), m_detail(0),
     m_showBranch(false), m_column(0), m_row(0), m_wide(false),
@@ -43,6 +45,8 @@
     m_font.setBold(false);
     m_font.setItalic(false);
     setCursor(Qt::ArrowCursor);
+
+    if (!m_star) m_star = new QImage(":images/star.png");
 }
 
 QString
@@ -68,6 +72,7 @@
     scene()->addItem(m_detail);
     int w = 100;
     if (m_wide) w = 180;
+    if (isMerge()) w = 60;
     int h = 80;
 //    m_detail->moveBy(x() - (m_detail->boundingRect().width() - 50) / 2,
 //                     y() + 60);
@@ -240,6 +245,22 @@
 void
 ChangesetItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
 {
+    if (isMerge()) {
+        paintMerge(paint);
+    } else {
+        paintNormal(paint);
+    }
+}
+
+bool
+ChangesetItem::isMerge() const
+{
+    return (m_changeset && m_changeset->parents().size() > 1);
+}
+
+void
+ChangesetItem::paintNormal(QPainter *paint)
+{
     paint->save();
     
     ColourSet *colourSet = ColourSet::instance();
@@ -303,30 +324,39 @@
 
     if (showProperLines) {
 
-        paint->setBrush(Qt::white);
+        if (m_new) {
+            paint->setBrush(QColor(255, 255, 220));
+        } else {
+            paint->setBrush(Qt::white);
+        }            
 
         if (m_current) {
-            paint->drawRect(QRectF(x0 - 4, -4, width + 5, height + 8));
-        }
-
-        if (m_new) {
-            paint->save();
-            paint->setPen(Qt::yellow);
-            paint->setBrush(Qt::NoBrush);
-            paint->drawRect(QRectF(x0 - 2, -2, width + 1, height + 4));
-            paint->restore();
+            paint->drawRoundedRect(QRectF(x0 - 4, -4, width + 5, height + 8),
+                                   10, 10);
+            if (m_new) {
+                paint->save();
+                paint->setPen(Qt::yellow);
+                paint->setBrush(Qt::NoBrush);
+                paint->drawRoundedRect(QRectF(x0 - 2, -2, width + 1, height + 4),
+                                       10, 10);
+                paint->restore();
+            }
         }
     }
 
-    paint->drawRect(r);
-
     if (!showText) {
+        paint->drawRoundedRect(r, 7, 7);
 	paint->restore();
 	return;
     }
 
-    paint->fillRect(QRectF(x0 + 0.5, 0.5, width - 4, fh - 0.5),
-		    QBrush(userColour));
+    paint->save();
+    paint->setPen(Qt::NoPen);
+    paint->drawRoundedRect(r, 7, 7);
+    paint->setBrush(QBrush(userColour));
+    paint->drawRoundedRect(QRectF(x0 + 0.5, 0.5, width - 4, fh - 0.5), 7, 7);
+    paint->drawRect(QRectF(x0 + 0.5, fh/2.0, width - 4, fh/2.0));
+    paint->restore();
 
     paint->setPen(QPen(Qt::white));
 
@@ -358,6 +388,10 @@
         }
     }
 
+    paint->setPen(QPen(branchColour, 2));
+    paint->setBrush(Qt::NoBrush);
+    paint->drawRoundedRect(r, 7, 7);
+
     if (m_showBranch) {
 	// write branch name
         paint->save();
@@ -373,6 +407,14 @@
         paint->restore();
     }
 
+    if (m_current && showProperLines) {
+        paint->setRenderHint(QPainter::SmoothPixmapTransform, true);
+        int starSize = fh * 1.5;
+        paint->drawImage(QRectF(x0 + width - starSize,
+                                -fh, starSize, starSize),
+                         *m_star);
+    }
+
     paint->setFont(f);
 
     for (int i = 0; i < lines.size(); ++i) {
@@ -381,3 +423,87 @@
 
     paint->restore();
 }
+
+void
+ChangesetItem::paintMerge(QPainter *paint)
+{
+    paint->save();
+    
+    ColourSet *colourSet = ColourSet::instance();
+    QColor branchColour = colourSet->getColourFor(m_changeset->branch());
+    QColor userColour = colourSet->getColourFor(m_changeset->author());
+
+    QFont f(m_font);
+
+    QTransform t = paint->worldTransform();
+    float scale = std::min(t.m11(), t.m22());
+    if (scale > 1.0) {
+	int ps = int((f.pixelSize() / scale) + 0.5);
+	if (ps < 8) ps = 8;
+	f.setPixelSize(ps);
+    }
+
+    bool showText = (scale >= 0.2);
+    bool showProperLines = (scale >= 0.1);
+
+    if (!showProperLines) {
+	paint->setPen(QPen(branchColour, 0));
+    } else {
+	paint->setPen(QPen(branchColour, 2));
+    }
+	
+    paint->setFont(f);
+    QFontMetrics fm(f);
+    int fh = fm.height();
+    int size = fh * 2;
+    int x0 = -size/2 + 25;
+
+    if (m_new) {
+        paint->setBrush(QColor(255, 255, 220));
+    } else {
+        paint->setBrush(Qt::white);
+    }
+
+    if (showProperLines) {
+
+        if (m_current) {
+            paint->drawEllipse(QRectF(x0 - 4, fh - 4, size + 8, size + 8));
+
+            if (m_new) {
+                paint->save();
+                paint->setPen(Qt::yellow);
+                paint->setBrush(Qt::NoBrush);
+                paint->drawEllipse(QRectF(x0 - 2, fh - 2, size + 4, size + 4));
+                paint->restore();
+            }
+        }
+    }
+
+    paint->drawEllipse(QRectF(x0, fh, size, size));
+
+    if (m_showBranch) {
+	// write branch name
+        paint->save();
+	f.setBold(true);
+	paint->setFont(f);
+	paint->setPen(QPen(branchColour));
+	QString branch = m_changeset->branch();
+        if (branch == "") branch = "default";
+	int wid = size * 3;
+	branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid);
+	paint->drawText(-wid/2 + 25, fm.ascent() - 4, branch);
+	f.setBold(false);
+        paint->restore();
+    }
+
+    if (m_current && showProperLines) {
+        paint->setRenderHint(QPainter::SmoothPixmapTransform, true);
+        int starSize = fh * 1.5;
+        paint->drawImage(QRectF(x0 + size - starSize/2,
+                                0, starSize, starSize),
+                         *m_star);
+    }
+
+    paint->restore();
+}
+
--- a/src/changesetitem.h	Tue May 24 13:29:27 2011 +0100
+++ b/src/changesetitem.h	Wed May 25 14:59:09 2011 +0100
@@ -25,6 +25,7 @@
 class ChangesetDetailItem;
 
 class QAction;
+class QImage;
 
 class ChangesetItem : public QGraphicsObject
 {
@@ -100,6 +101,12 @@
 
     QMap<QAction *, QString> m_parentDiffActions;
     QMap<QAction *, QString> m_summaryActions;
+
+    static QImage *m_star;
+
+    bool isMerge() const;
+    void paintNormal(QPainter *);
+    void paintMerge(QPainter *);
 };
 
 #endif // CHANGESETITEM_H
--- a/src/changesetscene.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/changesetscene.cpp	Wed May 25 14:59:09 2011 +0100
@@ -18,10 +18,16 @@
 #include "changesetscene.h"
 #include "changesetitem.h"
 #include "uncommitteditem.h"
-#include "dateitem.h"
+#include "debug.h"
+
+#include <QPainter>
+
 
 ChangesetScene::ChangesetScene()
-    : QGraphicsScene(), m_detailShown(0)
+    // Supply a non-NULL but trivial scene rect to inhibit automatic
+    // updates from QGraphicsScene, because we will set the rect
+    // explicitly in itemAddCompleted
+    : QGraphicsScene(QRectF(0, 0, 1, 1)), m_detailShown(0)
 {
 }
 
@@ -87,12 +93,30 @@
 }
 
 void
-ChangesetScene::addDateItem(DateItem *item)
+ChangesetScene::addDateRange(QString label, int minrow, int nrows, bool even)
 {
-    addItem(item);
+    DateRange dr;
+    dr.label = label;
+    dr.minrow = minrow;
+    dr.nrows = nrows;
+    dr.even = even;
+    m_dateRanges[minrow] = dr;
+}
 
-    connect(item, SIGNAL(clicked()),
-            this, SLOT(dateItemClicked()));
+void
+ChangesetScene::itemAddCompleted()
+{
+    QRectF r = itemsBoundingRect();
+    float minwidth = 300; //!!!
+    DEBUG << "ChangesetScene::itemAddCompleted: minwidth = " << minwidth
+          << ", r = " << r << endl;
+    if (r.width() < minwidth) {
+        float edgediff = (minwidth - r.width()) / 2;
+        r.setLeft(r.left() - edgediff);
+        r.setRight(r.right() + edgediff);
+    }
+    DEBUG << "ChangesetScene::itemAddCompleted: r now is " << r << endl;
+    setSceneRect(r);
 }
 
 void
@@ -114,12 +138,11 @@
 }
 
 void
-ChangesetScene::dateItemClicked()
+ChangesetScene::drawBackground(QPainter *paint, const QRectF &rect)
 {
-    if (m_detailShown) {
-        m_detailShown->hideDetail();
-    }
+    QGraphicsScene::drawBackground(paint, rect);
 }
+        
 
 ChangesetItem *
 ChangesetScene::getItemById(QString id)
--- a/src/changesetscene.h	Tue May 24 13:29:27 2011 +0100
+++ b/src/changesetscene.h	Wed May 25 14:59:09 2011 +0100
@@ -19,11 +19,11 @@
 #define CHANGESETSCENE_H
 
 #include <QGraphicsScene>
+#include <QMap>
 
 class ChangesetItem;
 class Changeset;
 class UncommittedItem;
-class DateItem;
 
 class ChangesetScene : public QGraphicsScene
 {
@@ -34,7 +34,20 @@
 
     void addChangesetItem(ChangesetItem *item);
     void addUncommittedItem(UncommittedItem *item);
-    void addDateItem(DateItem *item);
+
+    void addDateRange(QString label, int minrow, int nrows, bool even);
+
+    struct DateRange {
+        QString label;
+        int minrow;
+        int nrows;
+        bool even;
+    };
+
+    typedef QMap<int, DateRange> DateRanges; // key is minrow
+    DateRanges getDateRanges() const { return m_dateRanges; }
+
+    void itemAddCompleted(); // recalculate scene rect
 
     ChangesetItem *getItemById(QString id); // Slow: traversal required
 
@@ -58,10 +71,13 @@
 private slots:
     void changesetDetailShown();
     void changesetDetailHidden();
-    void dateItemClicked();
+
+protected:
+    void drawBackground(QPainter *, const QRectF &);
 
 private:
     ChangesetItem *m_detailShown;
+    DateRanges m_dateRanges;
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetview.cpp	Wed May 25 14:59:09 2011 +0100
@@ -0,0 +1,89 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2011 Chris Cannam
+    Copyright (c) 2011 Queen Mary, University of London
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "changesetview.h"
+#include "changesetscene.h"
+#include "colourset.h"
+#include "debug.h"
+
+#include <QScrollBar>
+
+ChangesetView::ChangesetView() :
+    Panned()
+{
+    connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
+	    this, SLOT(horizontalScrollHappened()));
+}
+
+void
+ChangesetView::horizontalScrollHappened()
+{
+    DEBUG << "ChangesetView::horizontalScrollHappened" << endl;
+    invalidateScene(rect(), QGraphicsScene::BackgroundLayer);
+    viewport()->update();
+}
+
+void
+ChangesetView::drawBackground(QPainter *paint, const QRectF &rect)
+{
+    DEBUG << "ChangesetView::drawBackground" << endl;
+
+    ChangesetScene *cs = qobject_cast<ChangesetScene *>(scene());
+
+    if (!cs) {
+	QGraphicsView::drawBackground(paint, rect);
+	return;
+    }
+
+    DEBUG << "ChangesetView::drawBackground: have scene" << endl;
+
+    ChangesetScene::DateRanges ranges = cs->getDateRanges();
+
+    paint->setClipRect(rect);
+
+    DEBUG << "clip rect is " << rect << endl;
+
+    paint->save();
+    QFont f(paint->font());
+    f.setBold(true);
+    paint->setFont(f);
+
+    float x = mapToScene(0, 0).x();
+    float w = mapToScene(width(), 0).x() - x;
+    float px = mapToScene(5, 0).x();
+
+    QBrush oddBrush(QColor::fromRgb(250, 250, 250));
+    QBrush evenBrush(QColor::fromRgb(240, 240, 240));
+
+    //!!! todo: select only the ranges actually within range!
+    
+    for (ChangesetScene::DateRanges::const_iterator i = ranges.begin();
+         i != ranges.end(); ++i) {
+
+	ChangesetScene::DateRange range = i.value();
+
+        QRectF r = QRectF(x, range.minrow * 90 - 25,
+			  w, range.nrows * 90).normalized();
+
+	paint->fillRect(r, range.even ? evenBrush : oddBrush);
+        paint->drawText(px, range.minrow * 90 - 10, range.label);
+    }
+
+    paint->restore();
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetview.h	Wed May 25 14:59:09 2011 +0100
@@ -0,0 +1,37 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2011 Chris Cannam
+    Copyright (c) 2011 Queen Mary, University of London
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef CHANGESETVIEW_H
+#define CHANGESETVIEW_H
+
+#include "panned.h"
+
+class ChangesetView : public Panned
+{
+    Q_OBJECT
+    
+public:
+    ChangesetView();
+
+private slots:
+    void horizontalScrollHappened();
+
+protected:
+    void drawBackground(QPainter *, const QRectF &);
+};
+
+#endif
--- a/src/colourset.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/colourset.cpp	Wed May 25 14:59:09 2011 +0100
@@ -38,9 +38,11 @@
     QColor c;
 
     if (m_colours.empty()) {
-	c = QColor::fromHsv(0, 200, 100);
+	c = QColor::fromHsv(0, 200, 150);
     } else {
-	c = QColor::fromHsv((m_lastColour.hue() + 70) % 360, 200, 100);
+        int hue = m_lastColour.hue() - 130;
+        if (hue < 0) hue += 360;
+	c = QColor::fromHsv(hue, 200, 150);
     }
 
     m_colours[n] = c;
--- a/src/connectionitem.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/connectionitem.cpp	Wed May 25 14:59:09 2011 +0100
@@ -41,9 +41,9 @@
     }
 
     return QRectF(xscale * c_col + size/2 - 2,
-		  yscale * c_row + size - 2,
-		  xscale * p_col - xscale * c_col + 4,
-		  yscale * p_row - yscale * c_row - size + 4)
+		  yscale * c_row + size - 22,
+		  xscale * p_col - xscale * c_col + 6,
+		  yscale * p_row - yscale * c_row - size + 44)
 	.normalized();
 }
 
@@ -125,6 +125,10 @@
 	}
     }
 
+    // ensure line reaches the node -- again doesn't matter if we
+    // overshoot
+    p.lineTo(p_x, yscale * p_row + 20);
+
     paint->drawPath(p);
     paint->restore();
 }
--- a/src/dateitem.cpp	Tue May 24 13:29:27 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include "dateitem.h"
-
-#include "debug.h"
-
-#include <QPainter>
-#include <QBrush>
-#include <QFont>
-#include <QGraphicsSceneMouseEvent>
-
-DateItem::DateItem() :
-    m_minrow(0), m_maxrow(0),
-    m_mincol(0), m_maxcol(0),
-    m_even(false)
-{
-}
-
-void
-DateItem::setRows(int minrow, int n)
-{
-    m_minrow = minrow;
-    m_maxrow = minrow + n - 1;
-    setY(m_minrow * 90);
-}
-
-void
-DateItem::setCols(int mincol, int n)
-{
-    m_mincol = mincol;
-    m_maxcol = mincol + n - 1;
-    setX(m_mincol * 100);
-}
-
-void
-DateItem::mousePressEvent(QGraphicsSceneMouseEvent *e)
-{
-    DEBUG << "DateItem::mousePressEvent" << endl;
-    if (e->button() == Qt::LeftButton) {
-        emit clicked();
-    }
-    e->ignore();
-}
-
-QRectF
-DateItem::boundingRect() const
-{
-    return QRectF(-75, -25,
-		  (m_maxcol - m_mincol + 1) * 100 + 100,
-		  (m_maxrow - m_minrow + 1) * 90).normalized();
-}
-
-void
-DateItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *opt, QWidget *w)
-{
-    QBrush brush;
-
-    if (m_even) {
-	QColor c(QColor::fromRgb(240, 240, 240));
-	brush = QBrush(c);
-    } else {
-	QColor c(QColor::fromRgb(250, 250, 250));
-	brush = QBrush(c);
-    }
-
-    paint->fillRect(boundingRect(), brush);
-
-    paint->save();
-    QFont f(paint->font());
-    f.setBold(true);
-    paint->setFont(f);
-    paint->drawText(-70, -10, m_dateString);
-    paint->restore();
-}
-
-
--- a/src/dateitem.h	Tue May 24 13:29:27 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef DATEITEM_H
-#define DATEITEM_H
-
-#include <QGraphicsObject>
-
-class DateItem : public QGraphicsObject
-{
-    Q_OBJECT
-
-public:
-    DateItem();
-
-    virtual QRectF boundingRect() const;
-    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
-
-    void setRows(int minrow, int n);
-    void setCols(int mincol, int n);
-
-    void setEven(bool e) { m_even = e; }
-
-    QString dateString() const { return m_dateString; }
-    void setDateString(QString s) { m_dateString = s; }
-
-signals:
-    void clicked();
-
-protected:
-    virtual void mousePressEvent(QGraphicsSceneMouseEvent *);
-
-private:
-    QString m_dateString;
-    int m_minrow;
-    int m_maxrow;
-    int m_mincol;
-    int m_maxcol;
-    bool m_even;
-};
-
-#endif // DATEITEM_H
--- a/src/grapher.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/grapher.cpp	Wed May 25 14:59:09 2011 +0100
@@ -17,7 +17,6 @@
 
 #include "grapher.h"
 #include "connectionitem.h"
-#include "dateitem.h"
 #include "debug.h"
 #include "changesetscene.h"
 
@@ -452,7 +451,7 @@
             ChangesetItem *pitem = m_items[p];
             conn->setParent(pitem);
             conn->setChild(m_uncommitted);
-            conn->setZValue(0);
+            conn->setZValue(-1);
             m_scene->addItem(conn);
             if (pitem) {
                 if (pitem->getChangeset()->branch() == uncommittedBranch) {
@@ -465,6 +464,9 @@
         // tell it it has a new branch (the "show branch" flag is set
         // elsewhere for this item)
         m_uncommitted->setIsNewBranch(!haveParentOnBranch);
+
+        // Uncommitted is a merge if it has more than one parent
+        m_uncommitted->setIsMerge(m_uncommittedParents.size() > 1);
     }
 
     // Add the branch labels
@@ -581,13 +583,7 @@
 
         if (date != prevDate) {
             if (prevDate != "") {
-                DateItem *item = new DateItem();
-                item->setDateString(prevDate);
-                item->setCols(datemincol, datemaxcol - datemincol + 1);
-                item->setRows(changeRow, n);
-                item->setEven(even);
-                item->setZValue(-2);
-                m_scene->addDateItem(item);
+                m_scene->addDateRange(prevDate, changeRow, n, even);
                 even = !even;
             }
             prevDate = date;
@@ -597,14 +593,10 @@
     }
     
     if (n > 0) {
-        DateItem *item = new DateItem();
-        item->setDateString(prevDate);
-        item->setCols(datemincol, datemaxcol - datemincol + 1);
-        item->setRows(changeRow, n+1);
-        item->setEven(even);
-        item->setZValue(-2);
-        m_scene->addDateItem(item);
+        m_scene->addDateRange(prevDate, changeRow, n+1, even);
         even = !even;
     }
+
+    m_scene->itemAddCompleted();
 }
 
--- a/src/historywidget.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/historywidget.cpp	Wed May 25 14:59:09 2011 +0100
@@ -18,7 +18,7 @@
 #include "historywidget.h"
 
 #include "changesetscene.h"
-#include "panned.h"
+#include "changesetview.h"
 #include "panner.h"
 #include "grapher.h"
 #include "debug.h"
@@ -32,11 +32,12 @@
     m_showUncommitted(false),
     m_refreshNeeded(false)
 {
-    m_panned = new Panned;
+    m_panned = new ChangesetView;
     m_panner = new Panner;
 
     m_panned->setDragMode(QGraphicsView::ScrollHandDrag);
     m_panned->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
+    m_panned->setCacheMode(QGraphicsView::CacheNone);
 
     QGridLayout *layout = new QGridLayout;
     layout->addWidget(m_panned, 0, 0);
--- a/src/panner.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/panner.cpp	Wed May 25 14:59:09 2011 +0100
@@ -32,7 +32,8 @@
 };
 
 Panner::Panner() :
-    m_clicked(false)
+    m_clicked(false),
+    m_moved(false)
 {
     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -209,6 +210,7 @@
         return;
     }
     m_clicked = true;
+    m_moved = false;
     m_clickedRect = m_pannedRect;
     m_clickedPoint = e->pos();
 }
@@ -231,6 +233,13 @@
     QPointF cp = mapToScene(m_clickedPoint);
     QPointF mp = mapToScene(e->pos());
     QPointF delta = mp - cp;
+    if (!m_moved) {
+        if ((m_clickedPoint - e->pos()).manhattanLength() > 2) {
+            m_moved = true;
+        } else {
+            return;
+        }
+    }
     QRectF nr = m_clickedRect;
     nr.translate(delta);
     m_pannedRect = nr;
@@ -247,7 +256,11 @@
     }
 
     if (m_clicked) {
-        mouseMoveEvent(e);
+        if (m_moved) {
+            mouseMoveEvent(e);
+        } else {
+            moveTo(e->pos());
+        }
     }
 
     m_clicked = false;
@@ -276,8 +289,9 @@
 {
     QPointF sp = mapToScene(p);
     QRectF nr = m_pannedRect;
-    double d = sp.x() - nr.center().x();
-    nr.translate(d, 0);
+    double dx = sp.x() - nr.center().x();
+    double dy = sp.y() - nr.center().y();
+    nr.translate(dx, dy);
     slotSetPannedRect(nr);
     emit pannedRectChanged(m_pannedRect);
     viewport()->update();
--- a/src/panner.h	Tue May 24 13:29:27 2011 +0100
+++ b/src/panner.h	Wed May 25 14:59:09 2011 +0100
@@ -68,6 +68,7 @@
                            const QStyleOptionGraphicsItem []);
 
     bool m_clicked;
+    bool m_moved;
     QRectF m_clickedRect;
     QPoint m_clickedPoint;
 
--- a/src/uncommitteditem.cpp	Tue May 24 13:29:27 2011 +0100
+++ b/src/uncommitteditem.cpp	Wed May 25 14:59:09 2011 +0100
@@ -29,7 +29,7 @@
 #include <QWidgetAction>
 
 UncommittedItem::UncommittedItem() :
-    m_showBranch(false), m_isNewBranch(false),
+    m_showBranch(false), m_isNewBranch(false), m_isMerge(false),
     m_column(0), m_row(0), m_wide(false)
 {
     m_font = QFont();
@@ -70,7 +70,10 @@
 UncommittedItem::activateMenu()
 {
     QMenu *menu = new QMenu;
-    QLabel *label = new QLabel(tr("<qt><b>&nbsp;Uncommitted changes</b></qt>"));
+    QLabel *label = new QLabel
+        (m_isMerge ?
+         tr("<qt><b>&nbsp;Uncommitted merge</b></qt>") :
+         tr("<qt><b>&nbsp;Uncommitted changes</b></qt>"));
     QWidgetAction *wa = new QWidgetAction(menu);
     wa->setDefaultWidget(label);
     menu->addAction(wa);
@@ -102,8 +105,17 @@
 }
 
 void
-UncommittedItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *option,
-		       QWidget *w)
+UncommittedItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
+{
+    if (isMerge()) {
+        paintMerge(paint);
+    } else {
+        paintNormal(paint);
+    }
+}
+
+void
+UncommittedItem::paintNormal(QPainter *paint)
 {
     paint->save();
     
@@ -137,7 +149,7 @@
     int height = 49;
     QRectF r(x0, 0, width - 3, height);
     paint->setBrush(Qt::white);
-    paint->drawRect(r);
+    paint->drawRoundedRect(r, 7, 7);
 
     if (m_wide) {
         QString label = tr("Uncommitted changes");
@@ -168,3 +180,66 @@
     paint->restore();
     return;
 }
+
+void
+UncommittedItem::paintMerge(QPainter *paint)
+{
+    paint->save();
+    
+    ColourSet *colourSet = ColourSet::instance();
+    QColor branchColour = colourSet->getColourFor(m_branch);
+
+    QFont f(m_font);
+
+    QTransform t = paint->worldTransform();
+    float scale = std::min(t.m11(), t.m22());
+    if (scale > 1.0) {
+	int ps = int((f.pixelSize() / scale) + 0.5);
+	if (ps < 8) ps = 8;
+	f.setPixelSize(ps);
+    }
+
+    if (scale < 0.1) {
+	paint->setPen(QPen(branchColour, 0, Qt::DashLine));
+    } else {
+	paint->setPen(QPen(branchColour, 2, Qt::DashLine));
+    }
+	
+    paint->setFont(f);
+    QFontMetrics fm(f);
+    int fh = fm.height();
+
+    int size = fh * 2;
+    int x0 = -size/2 + 25;
+
+    paint->setBrush(Qt::white);
+    paint->drawEllipse(QRectF(x0, fh, size, size));
+    
+    if (m_wide) {
+        QString label = tr("Uncommitted merge");
+        paint->drawText(size/2 + 28,
+                        25 - fm.height()/2 + fm.ascent(),
+                        label);
+    } else {
+        QString label = tr("Uncommitted");
+        paint->drawText(size/2 + 28,
+                        25 - fm.height() + fm.ascent(),
+                        label);
+        label = tr("merge");
+        paint->drawText(size/2 + 28,
+                        25 + fm.ascent(),
+                        label);
+    }        
+
+    if (m_showBranch && m_branch != "") {
+        // write branch name
+        f.setBold(true);
+        paint->setFont(f);
+	int wid = size * 3;
+	QString branch = TextAbbrev::abbreviate(m_branch, QFontMetrics(f), wid);
+	paint->drawText(-wid/2 + 25, fm.ascent() - 4, branch);
+    }
+
+    paint->restore();
+    return;
+}
--- a/src/uncommitteditem.h	Tue May 24 13:29:27 2011 +0100
+++ b/src/uncommitteditem.h	Wed May 25 14:59:09 2011 +0100
@@ -39,6 +39,9 @@
 
     bool isNewBranch() const { return m_isNewBranch; }
     void setIsNewBranch(bool s) { m_isNewBranch = s; }
+
+    bool isMerge() const { return m_isMerge; }
+    void setIsMerge(bool m) { m_isMerge = m; }
     
     int column() const { return m_column; }
     int row() const { return m_row; }
@@ -67,10 +70,14 @@
     QString m_branch;
     bool m_showBranch;
     bool m_isNewBranch;
+    bool m_isMerge;
     QFont m_font;
     int m_column;
     int m_row;
     bool m_wide;
+
+    void paintNormal(QPainter *);
+    void paintMerge(QPainter *);
 };
 
 #endif // UNCOMMITTEDITEM_H