changeset 45:4286836bb3c9

* Some more work on graph layout; ensure LANG is set for parseable UTF8 output when running Hg
author Chris Cannam <cannam@all-day-breakfast.com>
date Wed, 10 Nov 2010 12:44:11 +0000
parents bed7ab59f62e
children bd3accba9b3f
files grapher.cpp grapher.h hgexplorer.pro hgexpwidget.cpp hgexpwidget.h hgrunner.cpp panned.cpp panned.h panner.cpp panner.h
diffstat 10 files changed, 675 insertions(+), 104 deletions(-) [+]
line wrap: on
line diff
--- a/grapher.cpp	Tue Nov 09 17:51:12 2010 +0000
+++ b/grapher.cpp	Wed Nov 10 12:44:11 2010 +0000
@@ -1,87 +1,189 @@
 
 #include "grapher.h"
 
-#include <QSet>
-#include <QMap>
+#include <QGraphicsScene>
 
 #include <iostream>
 
-typedef QSet<int> ColumnSet;
-typedef QMap<int, ColumnSet> GridAlloc;
-typedef QMap<QString, Changeset *> IdChangesetMap;
-typedef QSet<Changeset *> ChangesetSet;
+int
+Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
+{
+    int col = parent;
+    if (preferParentCol) {
+	if (!m_alloc[row].contains(col)) {
+	    return col;
+	}
+    }
+    while (col > 0) {
+	if (!m_alloc[row].contains(--col)) return col;
+    }
+    while (col < 0) {
+	if (!m_alloc[row].contains(++col)) return col;
+    }
+    col = parent;
+    int sign = (col < 0 ? -1 : 1);
+    while (1) {
+	col += sign;
+	if (!m_alloc[row].contains(col)) return col;
+    }
+}
 
-ChangesetItem *
-layout(Changeset *cs,
-       IdChangesetMap idCsetMap,
-       ChangesetItemMap items,
-       GridAlloc &alloc,
-       ChangesetSet &handled)
+void
+Grapher::layoutRow(QString id)
 {
-    if (!cs) {
-	throw std::string("Null Changeset");
+    if (m_handled.contains(id)) {
+	return;
     }
-    if (!items.contains(cs)) {
-	throw std::string("Changeset not in item map");
+    if (!m_idCsetMap.contains(id)) {
+	throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
     }
-    ChangesetItem *item = items[cs];
-    if (handled.contains(cs)) {
-	return item;
+    if (!m_items.contains(id)) {
+	throw LayoutException(QString("Changeset %1 not in item map").arg(id));
     }
+    Changeset *cs = m_idCsetMap[id];
+    ChangesetItem *item = m_items[id];
+    std::cerr << "Looking at " << id.toStdString() << std::endl;
+
     int row = 0;
-    int col = 0;
-    if (!cs->parents().empty()) {
+    int nparents = cs->parents().size();
+
+    if (nparents > 0) {
 	bool haveRow = false;
 	foreach (QString parentId, cs->parents()) {
-	    if (parentId == "") continue; //!!!
-	    std::cerr << "recursing to parent \"" << parentId.toStdString() << "\" of \"" << cs->id().toStdString() << "\"" << std::endl;
-	    ChangesetItem *parentItem =
-		layout(idCsetMap[parentId],
-		       idCsetMap,
-		       items,
-		       alloc,
-		       handled);
+
+	    if (!m_idCsetMap.contains(parentId)) continue;
+	    if (!m_items.contains(parentId)) continue;
+
+	    if (!m_handled.contains(parentId)) {
+		layoutRow(parentId);
+	    }
+
+	    ChangesetItem *parentItem = m_items[parentId];
 	    if (!haveRow || parentItem->row() < row) {
 		row = parentItem->row();
 		haveRow = true;
 	    }
-	    col += parentItem->column();
 	}
-	col /= cs->parents().size();
 	row = row - 1;
-	while (alloc[row].contains(col)) {
-	    if (col > 0) col = -col;
-	    else col = -col + 1;
-	}
-	alloc[row].insert(col);
-    }	
-    item->setColumn(col);
+    }
+
+    std::cerr << "putting " << cs->id().toStdString() << " at row " << row 
+	      << std::endl;
+
     item->setRow(row);
-    item->setX(col * 100);
     item->setY(row * 100);
-    handled.insert(cs);
-    return item;
+    m_handled.insert(id);
 }
 
 void
-Grapher::layout(Changesets csets, ChangesetItemMap items)
+Grapher::layoutCol(QString id)
 {
-    IdChangesetMap idCsetMap;
-    foreach (Changeset *cs, csets) {
-	std::cerr << cs->id().toStdString() << std::endl;
-	if (cs->id() == "") {
-	    throw std::string("Changeset has no ID");
+    if (m_handled.contains(id)) {
+	std::cerr << "Already looked at " << id.toStdString() << std::endl;
+	return;
+    }
+    if (!m_idCsetMap.contains(id)) {
+	throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
+    }
+    if (!m_items.contains(id)) {
+	throw LayoutException(QString("Changeset %1 not in item map").arg(id));
+    }
+    Changeset *cs = m_idCsetMap[id];
+    ChangesetItem *item = m_items[id];
+    std::cerr << "Looking at " << id.toStdString() << std::endl;
+
+    int col = 0;
+    int nparents = cs->parents().size();
+
+    if (nparents > 0) {
+	bool preferParentCol = true;
+	foreach (QString parentId, cs->parents()) {
+
+	    if (!m_idCsetMap.contains(parentId)) continue;
+	    if (!m_items.contains(parentId)) continue;
+
+	    if (nparents == 1) {
+		// when introducing a new branch, aim _not_ to
+		// position child on the same column as parent
+		Changeset *parent = m_idCsetMap[parentId];
+		if (parent->branch() != cs->branch()) {
+		    preferParentCol = false;
+		}
+	    }		
+
+	    if (!m_handled.contains(parentId)) {
+		layoutCol(parentId);
+	    }
+
+	    ChangesetItem *parentItem = m_items[parentId];
+	    col += parentItem->column();
 	}
-	if (idCsetMap.contains(cs->id())) {
-	    throw std::string("Changeset ID is already in map");
-	}
-	idCsetMap[cs->id()] = cs;
+
+	col /= cs->parents().size();
+	col = findAvailableColumn(item->row(), col, preferParentCol);
+	m_alloc[item->row()].insert(col);
     }
 
-    GridAlloc alloc;
-    ChangesetSet handled;
+    std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl;
+
+    item->setColumn(col);
+    item->setX(col * 100);
+    m_handled.insert(id);
+}
+
+void
+Grapher::layout(Changesets csets)
+{
+    m_idCsetMap.clear();
+    m_items.clear();
+    m_alloc.clear();
+
     foreach (Changeset *cs, csets) {
-	::layout(cs, idCsetMap, items, alloc, handled);
+
+	QString id = cs->id();
+	std::cerr << id.toStdString() << std::endl;
+
+	if (id == "") {
+	    throw LayoutException("Changeset has no ID");
+	}
+	if (m_idCsetMap.contains(id)) {
+	    throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
+	}
+
+	m_idCsetMap[id] = cs;
+
+        ChangesetItem *item = new ChangesetItem(cs);
+        item->setX(0);
+        item->setY(0);
+	m_items[id] = item;
+        m_scene->addItem(item);
+    }
+
+    // Layout in reverse order, i.e. forward chronological order.
+    // This ensures that parents will normally be laid out before
+    // their children -- though we can recurse from layout() if we
+    // find any weird exceptions
+    m_handled.clear();
+    for (int i = csets.size() - 1; i >= 0; --i) {
+	layoutRow(csets[i]->id());
+    }
+    m_handled.clear();
+    for (int i = csets.size() - 1; i >= 0; --i) {
+	layoutCol(csets[i]->id());
+    }
+
+    foreach (Changeset *cs, csets) {
+	QString id = cs->id();
+	if (!m_items.contains(id)) continue;
+	ChangesetItem *me = m_items[id];
+	foreach (QString parentId, cs->parents()) {
+	    if (!m_items.contains(parentId)) continue;
+	    ChangesetItem *parent = m_items[parentId];
+	    QGraphicsLineItem *line = new QGraphicsLineItem;
+	    line->setLine(me->x() + 25, me->y() + 50,
+			  parent->x() + 25, parent->y());
+	    m_scene->addItem(line);
+	}
     }
 }
 
--- a/grapher.h	Tue Nov 09 17:51:12 2010 +0000
+++ b/grapher.h	Wed Nov 10 12:44:11 2010 +0000
@@ -4,12 +4,48 @@
 #include "changeset.h"
 #include "changesetitem.h"
 
-typedef QMap<Changeset *, ChangesetItem *> ChangesetItemMap;
+#include <QSet>
+#include <QMap>
+
+#include <exception>
 
 class Grapher
 {
 public:
-    void layout(Changesets csets, ChangesetItemMap items);
+    Grapher(QGraphicsScene *scene) { m_scene = scene; }
+
+    void layout(Changesets csets);
+
+    class LayoutException : public std::exception {
+    public:
+	LayoutException(QString message) throw() : m_message(message) { }
+	virtual ~LayoutException() throw() { }
+	virtual const char *what() const throw() {
+	    return m_message.toLocal8Bit().data();
+	}
+    protected:
+	QString m_message;
+    };
+
+private:
+    void layoutRow(QString id);
+    void layoutCol(QString id);
+    int findAvailableColumn(int row, int parent, bool preferParentCol);
+
+    QGraphicsScene *m_scene;
+
+    typedef QMap<QString, Changeset *> IdChangesetMap;
+    IdChangesetMap m_idCsetMap;
+
+    typedef QMap<QString, ChangesetItem *> IdItemMap;
+    IdItemMap m_items;
+
+    typedef QSet<int> ColumnSet;
+    typedef QMap<int, ColumnSet> GridAlloc;
+    GridAlloc m_alloc;
+
+    typedef QSet<QString> IdSet;
+    IdSet m_handled;
 };
 
 #endif 
--- a/hgexplorer.pro	Tue Nov 09 17:51:12 2010 +0000
+++ b/hgexplorer.pro	Wed Nov 10 12:44:11 2010 +0000
@@ -15,7 +15,9 @@
     settingsdialog.h \
     changeset.h \
     changesetitem.h \
-    logparser.h
+    logparser.h \
+    panner.h \
+    panned.h
 SOURCES = main.cpp \
     mainwindow.cpp \
     hgexpwidget.cpp \
@@ -25,7 +27,9 @@
     common.cpp \
     changeset.cpp \
     changesetitem.cpp \
-    logparser.cpp
+    logparser.cpp \
+    panner.cpp \
+    panned.cpp
 
 # ! [0]
 RESOURCES = hgexplorer.qrc
--- a/hgexpwidget.cpp	Tue Nov 09 17:51:12 2010 +0000
+++ b/hgexpwidget.cpp	Wed Nov 10 12:44:11 2010 +0000
@@ -12,6 +12,8 @@
 #include "changeset.h"
 #include "changesetitem.h"
 #include "grapher.h"
+#include "panner.h"
+#include "panned.h"
 
 #include <iostream>
 
@@ -91,7 +93,17 @@
     addTab(workPageWidget, tr("Work"));
 
     // History graph page
-    historyGraphPageWidget = new QGraphicsView;
+    historyGraphPageWidget = new QWidget;
+    Panned *panned = new Panned;
+    Panner *panner = new Panner;
+    historyGraphWidget = panned;
+    historyGraphPanner = panner;
+    QGridLayout *layout = new QGridLayout;
+    layout->addWidget(historyGraphWidget, 0, 0);
+    layout->addWidget(historyGraphPanner, 0, 1);
+    panner->setMaximumWidth(80);
+    panner->connectToPanned(panned);
+    historyGraphPageWidget->setLayout(layout);
     addTab(historyGraphPageWidget, tr("History (graph)"));
 
 
@@ -244,56 +256,20 @@
     localRepoHgLogList -> addItems(splitChangeSets(hgLogList));
 
     //!!!
-    QGraphicsView *gv = static_cast<QGraphicsView *>(historyGraphPageWidget);
-    gv->scene()->deleteLater();
+    Panned *panned = static_cast<Panned *>(historyGraphWidget);
+    Panner *panner = static_cast<Panner *>(historyGraphPanner);
     QGraphicsScene *scene = new QGraphicsScene();
     Changesets csets = parseChangeSets(hgLogList);
     if (csets.empty()) return;
-    ChangesetItemMap csetItemMap;
-    foreach (Changeset *cs, csets) {
-        ChangesetItem *item = new ChangesetItem(cs);
-        item->setX(0);
-        item->setY(0);
-	csetItemMap[cs] = item;
-        scene->addItem(item);
-    }
     try {
-	Grapher().layout(csets, csetItemMap);
+	Grapher(scene).layout(csets);
     } catch (std::string s) {
 	std::cerr << "Internal error: Layout failed: " << s << std::endl;
     }
-/*
-    QMap<QString, Changeset *> idCsetMap;
-    foreach (Changeset *cs, csets) {
-	if (cs->id() == "") {
-	    throw std::string("Changeset has no ID");
-	}
-	if (idCsetMap.contains(cs->id())) {
-	    throw std::string("Changeset ID is already in map");
-	}
-	idCsetMap[cs->id()] = cs;
-    }
-    typedef QSet<int> ColumnSet;
-    typedef QMap<int, ColumnSet> GridAlloc;
-    typedef QMap<Changeset *, ChangesetItem *> ChangesetItemMap;
-    ChangesetItemMap csetItemMap;
-    foreach (Changeset *cs, csets) {
-        ChangesetItem *item = new ChangesetItem(cs);
-        item->setX(0);
-        item->setY(-cs->number() * 100);
-	csetItemMap[cs] = item;
-        scene->addItem(item);
-    }
-    QSet<Changeset *> handled;
-    for (int i = csets.size() - 1; i >= 0; --i) {
-	Changeset *cs = csets[i];
-	if (handled.contains(cs)) continue;
-	
-	
-	handled.insert(cs);
-    }
-*/
-    gv->setScene(scene);
+    panned->scene()->deleteLater();
+    panned->setScene(scene);
+    panner->scene()->deleteLater();
+    panner->setScene(scene);
 }
 
 
--- a/hgexpwidget.h	Tue Nov 09 17:51:12 2010 +0000
+++ b/hgexpwidget.h	Wed Nov 10 12:44:11 2010 +0000
@@ -50,6 +50,8 @@
     QGroupBox   *grpRemoteRepo;
     QWidget     *workPageWidget;
     QWidget     *historyGraphPageWidget;
+    QWidget     *historyGraphWidget;
+    QWidget     *historyGraphPanner;
     QWidget     *historyPageWidget;
     QWidget     *headsPageWidget;
 
--- a/hgrunner.cpp	Tue Nov 09 17:51:12 2010 +0000
+++ b/hgrunner.cpp	Wed Nov 10 12:44:11 2010 +0000
@@ -13,6 +13,11 @@
 {
     proc = new QProcess(this);
 
+    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+    env.insert("LANG", "en_US.utf8");
+    env.insert("LC_ALL", "en_US.utf8");
+    proc->setProcessEnvironment(env);
+
     setTextVisible(false);
     setVisible(false);
     isRunning = false;
@@ -39,8 +44,8 @@
 {
     exitCode = procExitCode;
     exitStatus = procExitStatus;
-    stdOut = proc -> readAllStandardOutput();
-    stdErr = proc -> readAllStandardError();
+    stdOut = QString::fromUtf8(proc -> readAllStandardOutput());
+    stdErr = QString::fromUtf8(proc -> readAllStandardError());
     std::cerr << "stdout was " << stdOut.toStdString() << std::endl;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/panned.cpp	Wed Nov 10 12:44:11 2010 +0000
@@ -0,0 +1,90 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Rosegarden
+    A MIDI and audio sequencer and musical notation editor.
+    Copyright 2000-2010 the Rosegarden development team.
+
+    Other copyrights also apply to some parts of this work.  Please
+    see the AUTHORS file and individual file headers for details.
+
+    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 "panned.h"
+
+#include <QScrollBar>
+
+#include <iostream>
+
+Panned::Panned()
+{
+}
+
+void
+Panned::resizeEvent(QResizeEvent *ev)
+{
+    QPointF near = mapToScene(0, 0);
+    QPointF far = mapToScene(width(), height());
+    QSizeF sz(far.x()-near.x(), far.y()-near.y());
+    QRectF pr(near, sz);
+
+    if (pr != m_pannedRect) {
+        m_pannedRect = pr;
+        emit pannedRectChanged(pr);
+    }
+
+    QGraphicsView::resizeEvent(ev);
+}
+
+void
+Panned::paintEvent(QPaintEvent *e)
+{
+    QGraphicsView::paintEvent(e);
+}
+
+void
+Panned::drawForeground(QPainter *paint, const QRectF &)
+{
+    QPointF near = mapToScene(0, 0);
+    QPointF far = mapToScene(width(), height());
+    QSizeF sz(far.x()-near.x(), far.y()-near.y());
+    QRectF pr(near, sz);
+
+    if (pr != m_pannedRect) {
+        if (pr.x() != m_pannedRect.x()) emit pannedContentsScrolled();
+        m_pannedRect = pr;
+        emit pannedRectChanged(pr);
+    }
+}
+
+void
+Panned::slotSetPannedRect(QRectF pr)
+{
+    centerOn(pr.center());
+//	setSceneRect(pr);
+//	m_pannedRect = pr;
+}
+
+void
+Panned::wheelEvent(QWheelEvent *ev)
+{
+    emit wheelEventReceived(ev);
+    QGraphicsView::wheelEvent(ev);
+}
+
+void
+Panned::slotEmulateWheelEvent(QWheelEvent *ev)
+{
+    QGraphicsView::wheelEvent(ev);
+}
+
+void
+Panned::leaveEvent(QEvent *)
+{
+    emit mouseLeaves();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/panned.h	Wed Nov 10 12:44:11 2010 +0000
@@ -0,0 +1,55 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Rosegarden
+    A MIDI and audio sequencer and musical notation editor.
+    Copyright 2000-2010 the Rosegarden development team.
+ 
+    Other copyrights also apply to some parts of this work.  Please
+    see the AUTHORS file and individual file headers for details.
+ 
+    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 _RG_PANNED_H_
+#define _RG_PANNED_H_
+
+#include <QGraphicsView>
+
+class QWheelEvent;
+class QEvent;
+
+class Panned : public QGraphicsView
+{
+    Q_OBJECT
+
+public:
+    Panned();
+    virtual ~Panned() { }
+
+signals:
+    void pannedRectChanged(QRectF);
+    void wheelEventReceived(QWheelEvent *);
+    void pannedContentsScrolled();
+    void mouseLeaves();
+
+public slots:
+    void slotSetPannedRect(QRectF);
+    void slotEmulateWheelEvent(QWheelEvent *ev);
+
+protected:
+    QRectF m_pannedRect;
+
+    virtual void paintEvent(QPaintEvent *);
+    virtual void resizeEvent(QResizeEvent *);
+    virtual void drawForeground(QPainter *, const QRectF &);
+    virtual void wheelEvent(QWheelEvent *);
+    virtual void leaveEvent(QEvent *);
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/panner.cpp	Wed Nov 10 12:44:11 2010 +0000
@@ -0,0 +1,226 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Rosegarden
+    A MIDI and audio sequencer and musical notation editor.
+    Copyright 2000-2010 the Rosegarden development team.
+ 
+    Other copyrights also apply to some parts of this work.  Please
+    see the AUTHORS file and individual file headers for details.
+ 
+    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 "panner.h"
+#include "panned.h"
+
+#include <QPolygon>
+#include <QMouseEvent>
+#include <QColor>
+
+#include <iostream>
+
+class PannerScene : public QGraphicsScene
+{
+public:
+    friend class Panner;
+};
+
+Panner::Panner() :
+    m_clicked(false)
+{
+    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    setOptimizationFlags(QGraphicsView::DontSavePainterState);
+    setMouseTracking(true);
+    setInteractive(false);
+}
+
+void
+Panner::setScene(QGraphicsScene *s)
+{
+    if (scene()) {
+        disconnect(scene(), SIGNAL(sceneRectChanged(const QRectF &)),
+                   this, SLOT(slotSceneRectChanged(const QRectF &)));
+    }
+    QGraphicsView::setScene(s);
+    if (scene()) fitInView(sceneRect(), Qt::IgnoreAspectRatio);
+    m_cache = QPixmap();
+    connect(scene(), SIGNAL(sceneRectChanged(const QRectF &)),
+            this, SLOT(slotSceneRectChanged(const QRectF &)));
+}
+
+void
+Panner::connectToPanned(Panned *p)
+{
+    connect(p, SIGNAL(pannedRectChanged(QRectF)),
+            this, SLOT(slotSetPannedRect(QRectF)));
+
+    connect(this, SIGNAL(pannedRectChanged(QRectF)),
+            p, SLOT(slotSetPannedRect(QRectF)));
+}
+
+void
+Panner::slotSetPannedRect(QRectF rect) 
+{
+    m_pannedRect = rect;
+    viewport()->update();
+}
+
+void
+Panner::resizeEvent(QResizeEvent *)
+{
+    if (scene()) fitInView(sceneRect(), Qt::IgnoreAspectRatio);
+    m_cache = QPixmap();
+}
+
+void
+Panner::slotSceneRectChanged(const QRectF &newRect)
+{
+    if (!scene()) return; // spurious
+    fitInView(newRect, Qt::IgnoreAspectRatio);
+    m_cache = QPixmap();
+    viewport()->update();
+}
+
+void
+Panner::paintEvent(QPaintEvent *e)
+{
+    QPaintEvent *e2 = new QPaintEvent(e->region().boundingRect());
+    QGraphicsView::paintEvent(e2);
+
+    QPainter paint;
+    paint.begin(viewport());
+    paint.setClipRegion(e->region());
+
+    QPainterPath path;
+    path.addRect(rect());
+    path.addPolygon(mapFromScene(m_pannedRect));
+
+    QColor c(QColor::fromHsv(211, 194, 238));
+    c.setAlpha(80);
+    paint.setPen(Qt::NoPen);
+    paint.setBrush(c);
+    paint.drawPath(path);
+
+    paint.setBrush(Qt::NoBrush);
+    paint.setPen(QPen(QColor::fromHsv(211, 194, 238), 0));
+    paint.drawConvexPolygon(mapFromScene(m_pannedRect));
+
+    paint.end();
+
+    emit pannerChanged(m_pannedRect);
+}
+
+void
+Panner::updateScene(const QList<QRectF> &rects)
+{
+    if (!m_cache.isNull()) m_cache = QPixmap();
+    QGraphicsView::updateScene(rects);
+}
+
+void
+Panner::drawItems(QPainter *painter, int numItems,
+                  QGraphicsItem *items[],
+                  const QStyleOptionGraphicsItem options[])
+{
+    if (m_cache.size() != viewport()->size()) {
+
+        QGraphicsScene *s = scene();
+        if (!s) return;
+        PannerScene *ps = static_cast<PannerScene *>(s);
+
+        m_cache = QPixmap(viewport()->size());
+        m_cache.fill(Qt::transparent);
+        QPainter cachePainter;
+        cachePainter.begin(&m_cache);
+        cachePainter.setTransform(viewportTransform());
+        ps->drawItems(&cachePainter, numItems, items, options);
+        cachePainter.end();
+    }
+
+    painter->save();
+    painter->setTransform(QTransform());
+    painter->drawPixmap(0, 0, m_cache);
+    painter->restore();
+}
+ 
+void
+Panner::mousePressEvent(QMouseEvent *e)
+{
+    if (e->button() != Qt::LeftButton) {
+        QGraphicsView::mouseDoubleClickEvent(e);
+        return;
+    }
+    m_clicked = true;
+    m_clickedRect = m_pannedRect;
+    m_clickedPoint = e->pos();
+}
+
+void
+Panner::mouseDoubleClickEvent(QMouseEvent *e)
+{
+    if (e->button() != Qt::LeftButton) {
+        QGraphicsView::mouseDoubleClickEvent(e);
+        return;
+    }
+
+    moveTo(e->pos());
+}
+
+void
+Panner::mouseMoveEvent(QMouseEvent *e)
+{
+    if (!m_clicked) return;
+    QPointF cp = mapToScene(m_clickedPoint);
+    QPointF mp = mapToScene(e->pos());
+    QPointF delta = mp - cp;
+    QRectF nr = m_clickedRect;
+    nr.translate(delta);
+    slotSetPannedRect(nr);
+    emit pannedRectChanged(m_pannedRect);
+    viewport()->update();
+}
+
+void
+Panner::mouseReleaseEvent(QMouseEvent *e)
+{
+    if (e->button() != Qt::LeftButton) {
+        QGraphicsView::mouseDoubleClickEvent(e);
+        return;
+    }
+
+    if (m_clicked) {
+        mouseMoveEvent(e);
+    }
+
+    m_clicked = false;
+    viewport()->update();
+}
+
+void
+Panner::wheelEvent(QWheelEvent *e)
+{
+    if (e->delta() > 0) {
+        emit zoomOut();
+    } else {
+        emit zoomIn();
+    }
+}
+
+void
+Panner::moveTo(QPoint p)
+{
+    QPointF sp = mapToScene(p);
+    QRectF nr = m_pannedRect;
+    double d = sp.x() - nr.center().x();
+    nr.translate(d, 0);
+    slotSetPannedRect(nr);
+    emit pannedRectChanged(m_pannedRect);
+    viewport()->update();
+}
+   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/panner.h	Wed Nov 10 12:44:11 2010 +0000
@@ -0,0 +1,75 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Rosegarden
+    A MIDI and audio sequencer and musical notation editor.
+    Copyright 2000-2010 the Rosegarden development team.
+ 
+    Other copyrights also apply to some parts of this work.  Please
+    see the AUTHORS file and individual file headers for details.
+ 
+    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 _RG_PANNER_H_
+#define _RG_PANNER_H_
+
+#include <QGraphicsView>
+
+class Panned;
+
+class Panner : public QGraphicsView
+{
+    Q_OBJECT
+
+public:
+    Panner();
+    virtual ~Panner() { }
+
+    virtual void setScene(QGraphicsScene *);
+
+    void connectToPanned(Panned *p);
+
+signals:
+    void pannedRectChanged(QRectF);
+    void pannerChanged(QRectF);
+    void zoomIn();
+    void zoomOut();
+
+public slots:
+    void slotSetPannedRect(QRectF);
+
+protected slots:
+    void slotSceneRectChanged(const QRectF &);
+
+protected:
+    QRectF m_pannedRect;
+
+    void moveTo(QPoint);
+
+    virtual void paintEvent(QPaintEvent *);
+    virtual void mousePressEvent(QMouseEvent *e);
+    virtual void mouseMoveEvent(QMouseEvent *e);
+    virtual void mouseReleaseEvent(QMouseEvent *e);
+    virtual void mouseDoubleClickEvent(QMouseEvent *e);
+    virtual void wheelEvent(QWheelEvent *e);
+
+    virtual void resizeEvent(QResizeEvent *);
+
+    virtual void updateScene(const QList<QRectF> &);
+    virtual void drawItems(QPainter *, int, QGraphicsItem *[],
+                           const QStyleOptionGraphicsItem []);
+
+    bool m_clicked;
+    QRectF m_clickedRect;
+    QPoint m_clickedPoint;
+
+    QPixmap m_cache;
+};
+
+#endif
+