Mercurial > hg > easyhg
changeset 46:bd3accba9b3f
* Better layout for branches; spline connection paths
author | Chris Cannam |
---|---|
date | Wed, 10 Nov 2010 17:11:41 +0000 |
parents | 4286836bb3c9 |
children | 24efab584ee5 |
files | changesetitem.cpp changesetitem.h connectionitem.cpp connectionitem.h grapher.cpp grapher.h hgexplorer.pro panned.cpp panner.cpp |
diffstat | 9 files changed, 243 insertions(+), 44 deletions(-) [+] |
line wrap: on
line diff
--- a/changesetitem.cpp Wed Nov 10 12:44:11 2010 +0000 +++ b/changesetitem.cpp Wed Nov 10 17:11:41 2010 +0000 @@ -6,7 +6,6 @@ QRectF ChangesetItem::boundingRect() const { - int n = m_changeset->number(); return QRectF(0, 0, 250, 50); }
--- a/changesetitem.h Wed Nov 10 12:44:11 2010 +0000 +++ b/changesetitem.h Wed Nov 10 17:11:41 2010 +0000 @@ -15,8 +15,8 @@ int column() const { return m_column; } int row() const { return m_row; } - void setColumn(int c) { m_column = c; } - void setRow(int r) { m_row = r; } + void setColumn(int c) { m_column = c; setX(c * 100); } + void setRow(int r) { m_row = r; setY(r * 100); } private: Changeset *m_changeset;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/connectionitem.cpp Wed Nov 10 17:11:41 2010 +0000 @@ -0,0 +1,48 @@ + + +#include "connectionitem.h" + +#include "changesetitem.h" + +#include <QPainter> + +QRectF +ConnectionItem::boundingRect() const +{ + if (!m_parent || !m_child) return QRectF(); + float scale = 100; + float size = 50; + return QRectF(scale * m_child->column() + size/2 - 2, + scale * m_child->row() + size - 2, + scale * m_parent->column() - scale * m_child->column() + 4, + scale * m_parent->row() - scale * m_child->row() - size + 4) + .normalized(); +} + +void +ConnectionItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *) +{ + QPainterPath p; + float scale = 100; + float size = 50; + p.moveTo(scale * m_child->column() + size/2, + scale * m_child->row() + size); + if (m_parent->column() == m_child->column()) { + p.lineTo(scale * m_parent->column() + size/2, + scale * m_parent->row()); + } else { + p.cubicTo(scale * m_child->column() + size/2, + scale * m_child->row() + size + size, + scale * m_parent->column() + size/2, + scale * m_child->row() + size, + scale * m_parent->column() + size/2, + scale * m_child->row() + scale); + if (abs(m_parent->row() - m_child->row()) > 1) { + p.lineTo(scale * m_parent->column() + size/2, + scale * m_parent->row()); + } + } + paint->drawPath(p); +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/connectionitem.h Wed Nov 10 17:11:41 2010 +0000 @@ -0,0 +1,31 @@ +#ifndef CONNECTIONITEM_H +#define CONNECTIONITEM_H + +#include <QGraphicsItem> + +class Connection; + +class ChangesetItem; + +class ConnectionItem : public QGraphicsItem +{ +public: + ConnectionItem() : m_parent(0), m_child(0) { } + + virtual QRectF boundingRect() const; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + //!!! deletion signals from parent/child + + ChangesetItem *parent() { return m_parent; } + ChangesetItem *child() { return m_child; } + + void setParent(ChangesetItem *p) { m_parent = p; } + void setChild(ChangesetItem *c) { m_child = c; } + +private: + ChangesetItem *m_parent; + ChangesetItem *m_child; +}; + +#endif // CONNECTIONITEM_H
--- a/grapher.cpp Wed Nov 10 12:44:11 2010 +0000 +++ b/grapher.cpp Wed Nov 10 17:11:41 2010 +0000 @@ -1,5 +1,6 @@ #include "grapher.h" +#include "connectionitem.h" #include <QGraphicsScene> @@ -34,13 +35,13 @@ if (m_handled.contains(id)) { return; } - if (!m_idCsetMap.contains(id)) { + if (!m_changesets.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]; + Changeset *cs = m_changesets[id]; ChangesetItem *item = m_items[id]; std::cerr << "Looking at " << id.toStdString() << std::endl; @@ -51,7 +52,7 @@ bool haveRow = false; foreach (QString parentId, cs->parents()) { - if (!m_idCsetMap.contains(parentId)) continue; + if (!m_changesets.contains(parentId)) continue; if (!m_items.contains(parentId)) continue; if (!m_handled.contains(parentId)) { @@ -71,7 +72,6 @@ << std::endl; item->setRow(row); - item->setY(row * 100); m_handled.insert(id); } @@ -82,61 +82,149 @@ std::cerr << "Already looked at " << id.toStdString() << std::endl; return; } - if (!m_idCsetMap.contains(id)) { + if (!m_changesets.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]; + Changeset *cs = m_changesets[id]; ChangesetItem *item = m_items[id]; std::cerr << "Looking at " << id.toStdString() << std::endl; + foreach (QString parentId, cs->parents()) { + if (!m_changesets.contains(parentId)) continue; + if (!m_handled.contains(parentId)) { + layoutCol(parentId); + } + } + int col = 0; + int row = item->row(); + QString branch = cs->branch(); int nparents = cs->parents().size(); + QString parentId; + int parentsOnSameBranch = 0; - if (nparents > 0) { - bool preferParentCol = true; - foreach (QString parentId, cs->parents()) { + switch (nparents) { - if (!m_idCsetMap.contains(parentId)) continue; - if (!m_items.contains(parentId)) continue; + case 0: + col = m_branchHomes[cs->branch()]; + col = findAvailableColumn(row, col, true); + break; - 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; - } - } + case 1: + parentId = cs->parents()[0]; - if (!m_handled.contains(parentId)) { - layoutCol(parentId); - } - - ChangesetItem *parentItem = m_items[parentId]; - col += parentItem->column(); + if (!m_changesets.contains(parentId) || + m_changesets[parentId]->branch() != branch) { + // new branch + col = m_branchHomes[branch]; + } else { + col = m_items[parentId]->column(); } - col /= cs->parents().size(); - col = findAvailableColumn(item->row(), col, preferParentCol); - m_alloc[item->row()].insert(col); + col = findAvailableColumn(row, col, true); + break; + + case 2: + // a merge: behave differently if parents are both on the same + // branch (we also want to behave differently for nodes that + // have multiple children on the same branch -- spreading them + // out rather than having affinity to a specific branch) + + foreach (QString parentId, cs->parents()) { + if (!m_changesets.contains(parentId)) continue; + if (m_changesets[parentId]->branch() == branch) { + ChangesetItem *parentItem = m_items[parentId]; + col += parentItem->column(); + parentsOnSameBranch++; + } + } + + if (parentsOnSameBranch > 0) { + col /= parentsOnSameBranch; + col = findAvailableColumn(item->row(), col, true); + } else { + col = findAvailableColumn(item->row(), col, false); + } + break; } std::cerr << "putting " << cs->id().toStdString() << " at col " << col << std::endl; + m_alloc[row].insert(col); item->setColumn(col); - item->setX(col * 100); m_handled.insert(id); } +bool +Grapher::rangesConflict(const Range &r1, const Range &r2) +{ + // allow some additional space at edges. we really ought also to + // permit space at the end of a branch up to the point where the + // merge happens + int a1 = r1.first - 2, b1 = r1.second + 2; + int a2 = r2.first - 2, b2 = r2.second + 2; + if (a1 > b2 || b1 < a2) return false; + if (a2 > b1 || b2 < a1) return false; + return true; +} + +void +Grapher::allocateBranchHomes(Changesets csets) +{ + foreach (Changeset *cs, csets) { + QString branch = cs->branch(); + ChangesetItem *item = m_items[cs->id()]; + if (!item) continue; + int row = item->row(); + if (!m_branchRanges.contains(branch)) { + m_branchRanges[branch] = Range(row, row); + } else { + Range p = m_branchRanges[branch]; + if (row < p.first) p.first = row; + if (row > p.second) p.second = row; + m_branchRanges[branch] = p; + } + } + + m_branchHomes[""] = 0; + + foreach (QString branch, m_branchRanges.keys()) { + if (branch == "") continue; + QSet<int> taken; + taken.insert(0); + Range myRange = m_branchRanges[branch]; + foreach (QString other, m_branchRanges.keys()) { + if (other == branch || other == "") continue; + Range otherRange = m_branchRanges[other]; + if (rangesConflict(myRange, otherRange)) { + if (m_branchHomes.contains(other)) { + taken.insert(m_branchHomes[other]); + } + } + } + int home = 2; + while (taken.contains(home)) { + if (home > 0) home = -home; + else home = -(home-2); + } + m_branchHomes[branch] = home; + } + + foreach (QString branch, m_branchRanges.keys()) { + std::cerr << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << std::endl; + } +} + void Grapher::layout(Changesets csets) { - m_idCsetMap.clear(); + m_changesets.clear(); m_items.clear(); m_alloc.clear(); + m_branchHomes.clear(); foreach (Changeset *cs, csets) { @@ -146,11 +234,11 @@ if (id == "") { throw LayoutException("Changeset has no ID"); } - if (m_idCsetMap.contains(id)) { + if (m_changesets.contains(id)) { throw LayoutException(QString("Duplicate changeset ID %1").arg(id)); } - m_idCsetMap[id] = cs; + m_changesets[id] = cs; ChangesetItem *item = new ChangesetItem(cs); item->setX(0); @@ -159,6 +247,18 @@ m_scene->addItem(item); } + foreach (Changeset *cs, csets) { + QString id = cs->id(); + ChangesetItem *item = m_items[id]; + foreach (QString parentId, cs->parents()) { + if (!m_items.contains(parentId)) continue; + ConnectionItem *conn = new ConnectionItem(); + conn->setChild(item); + conn->setParent(m_items[parentId]); + m_scene->addItem(conn); + } + } + // 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 @@ -167,11 +267,14 @@ for (int i = csets.size() - 1; i >= 0; --i) { layoutRow(csets[i]->id()); } + + allocateBranchHomes(csets); + 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; @@ -185,5 +288,6 @@ m_scene->addItem(line); } } +*/ }
--- a/grapher.h Wed Nov 10 12:44:11 2010 +0000 +++ b/grapher.h Wed Nov 10 17:11:41 2010 +0000 @@ -6,6 +6,7 @@ #include <QSet> #include <QMap> +#include <QPair> #include <exception> @@ -28,14 +29,10 @@ }; 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; + IdChangesetMap m_changesets; typedef QMap<QString, ChangesetItem *> IdItemMap; IdItemMap m_items; @@ -44,8 +41,21 @@ typedef QMap<int, ColumnSet> GridAlloc; GridAlloc m_alloc; + typedef QPair<int, int> Range; + typedef QMap<QString, Range> BranchRangeMap; + BranchRangeMap m_branchRanges; + + typedef QMap<QString, int> BranchColumnMap; + BranchColumnMap m_branchHomes; + typedef QSet<QString> IdSet; IdSet m_handled; + + void layoutRow(QString id); + void layoutCol(QString id); + void allocateBranchHomes(Changesets csets); + bool rangesConflict(const Range &r1, const Range &r2); + int findAvailableColumn(int row, int parent, bool preferParentCol); }; #endif
--- a/hgexplorer.pro Wed Nov 10 12:44:11 2010 +0000 +++ b/hgexplorer.pro Wed Nov 10 17:11:41 2010 +0000 @@ -17,7 +17,8 @@ changesetitem.h \ logparser.h \ panner.h \ - panned.h + panned.h \ + connectionitem.h SOURCES = main.cpp \ mainwindow.cpp \ hgexpwidget.cpp \ @@ -29,7 +30,8 @@ changesetitem.cpp \ logparser.cpp \ panner.cpp \ - panned.cpp + panned.cpp \ + connectionitem.cpp # ! [0] RESOURCES = hgexplorer.qrc @@ -37,4 +39,4 @@ RC_FILE = hgexplorer.rc } -QT += network +QT += network opengl
--- a/panned.cpp Wed Nov 10 12:44:11 2010 +0000 +++ b/panned.cpp Wed Nov 10 17:11:41 2010 +0000 @@ -18,11 +18,14 @@ #include "panned.h" #include <QScrollBar> +#include <QGLWidget> #include <iostream> Panned::Panned() { +// setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); + setRenderHints(QPainter::Antialiasing); } void
--- a/panner.cpp Wed Nov 10 12:44:11 2010 +0000 +++ b/panner.cpp Wed Nov 10 17:11:41 2010 +0000 @@ -21,6 +21,7 @@ #include <QPolygon> #include <QMouseEvent> #include <QColor> +#include <QGLWidget> #include <iostream> @@ -33,6 +34,7 @@ Panner::Panner() : m_clicked(false) { +// setViewport(new QGLWidget()); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setOptimizationFlags(QGraphicsView::DontSavePainterState);