#
#
# add_file "src/model/Graph.cpp"
# content [fa58d4e3fd6ebe3f1beb05a9682b7efced6ffed1]
#
# add_file "src/model/Graph.h"
# content [12331956dd8457649a45dbfe59f4f10b48676d9d]
#
# patch "guitone.pro"
# from [b38ecc510a2044518ac07a6c054bda668ede603f]
# to [30b599d7104bebd31fd40b59aea1eb04fd4cdfe1]
#
============================================================
--- src/model/Graph.cpp fa58d4e3fd6ebe3f1beb05a9682b7efced6ffed1
+++ src/model/Graph.cpp fa58d4e3fd6ebe3f1beb05a9682b7efced6ffed1
@@ -0,0 +1,275 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Thomas Keller *
+ * address@hidden *
+ * *
+ * 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 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#include "Graph.h"
+
+Graph::Graph(const DatabaseFile & db) : databaseFile(db) {};
+
+Graph::~Graph() {};
+
+void Graph::readGraph()
+{
+ AutomateCommand::enqueueDatabaseTask(
+ databaseFile,
+ MonotoneTask(QStringList() << "graph")
+ );
+}
+
+void Graph::filterBranch(const QString & branch)
+{
+ AutomateCommand::enqueueDatabaseTask(
+ databaseFile,
+ MonotoneTask(QStringList() << "select" << "b:"+branch)
+ );
+}
+
+void Graph::processTaskResult(const MonotoneTask & task)
+{
+ if (task.getReturnCode() != 0)
+ {
+ C(QString("Command returned with a non-zero return code (%1)")
+ .arg(task.getOutputUtf8()));
+ return;
+ }
+
+ QString cmd = QString(task.getArguments().at(0));
+ QString output = task.getOutputUtf8();
+
+ if (cmd == "graph")
+ {
+ QStringList revisionLists = output.split("\n", QString::SkipEmptyParts);
+
+ foreach (QString revisionList, revisionLists)
+ {
+ QStringList revisions = revisionList.split(" ", QString::SkipEmptyParts);
+ QString rev = revisions.takeFirst();
+ I(revisionMap.contains(rev));
+ Revision * r = new Revision();
+ revisionMap.insert(rev, r);
+
+ r->hash = rev;
+ r->parents = revisions;
+ }
+
+ QMapIterator it(revisionMap);
+
+ while (it.hasNext())
+ {
+ it.next();
+ Revision * r = it.value();
+ foreach (QString parent, r->parents)
+ {
+ I(revisionMap.contains(parent));
+ revisionMap[parent]->children.append(r->hash);
+ }
+ }
+ return;
+ }
+
+ if (cmd == "branch")
+ {
+ QString branch = QString(task.getArguments().at(1));
+ QStringList revisions = output.split("\n", QString::SkipEmptyParts);
+
+ foreach (QString rev, revisions)
+ {
+ I(revisionMap.contains(rev));
+ revisionMap[rev]->branches.append(branch);
+ }
+
+ currentBranch = branch;
+
+ AutomateCommand::enqueueDatabaseTask(
+ databaseFile,
+ QStringList() << "heads" << branch
+ );
+ return;
+ }
+
+ if (cmd == "heads")
+ {
+ QString branch = QString(task.getArguments().at(1));
+ QStringList heads = output.split("\n", QString::SkipEmptyParts);
+
+ branchHeads.insert(branch, heads);
+ buildLanes(heads, QStringList());
+ return;
+ }
+}
+
+void Graph::buildLanes(QStringList & newRevisions, const QStringList & lastRevisions)
+{
+ QString rev = newRevisions.takeFirst();
+ QStringList nextRevisions = lastRevisions;
+
+ QList lanes;
+ bool removedDeadLine = false;
+
+ // initialize all lanes with plain vertical bars first;
+ // if we stumble upon dead lines (empty revisions), we re-use
+ // their slot for the next revision(s) - but only one lane per row
+ for (int i=0; i 0);
+ i--;
+ removedDeadLine = true;
+ continue;
+ }
+
+ // preserve further dead lines
+ if (nextRevisions.at(i).isEmpty())
+ {
+ lanes.push_back(Revision::Empty);
+ }
+ else
+ {
+ lanes.push_back(removedDeadLine ? Revision::BarToLeft
+ : Revision::Bar);
+ }
+ }
+
+ I(lanes.size() == nextRevisions.size());
+
+ Revision * revObj = revisionMap.value(rev);
+ int idx = lastRevisions.indexOf(rev);
+
+ if (idx == -1)
+ {
+ Revision::LaneType type;
+ switch (revObj->parents.size())
+ {
+ case 0: type = Revision::SingleNode; break;
+ case 1: type = Revision::Head; break;
+ case 2: type = Revision::HeadMerge; break;
+ default: I(false);
+ }
+ nextRevisions += revObj->parents;
+ lanes.push_back(type);
+ }
+ else
+ {
+ // add revisions for the next round
+ newRevisions += revObj->parents;
+
+ Revision::LaneType type, defaultNextType;
+ switch (revObj->parents.size())
+ {
+ case 0:
+ type = Revision::Tail;
+ defaultNextType = Revision::BarToLeft;
+ nextRevisions.replace(idx, QString());
+ break;
+ case 1:
+ type = Revision::Node;
+ defaultNextType = Revision::Bar;
+ nextRevisions.replace(idx, revObj->parents.at(0));
+ break;
+ case 2:
+ type = Revision::Merge;
+ nextRevisions.replace(idx, revObj->parents.at(0));
+
+ // check the special case where a dead line is right
+ // beside the current merge node, which allows us to
+ // re-use its space
+ if (idx + 1 < nextRevisions.size() &&
+ nextRevisions.at(idx + 1).isEmpty())
+ {
+ defaultNextType = Revision::Bar;
+ nextRevisions.replace(idx + 1, revObj->parents.at(1));
+ }
+ else
+ {
+ defaultNextType = Revision::BarToRight;
+ nextRevisions.insert(idx + 1, revObj->parents.at(1));
+ }
+ break;
+ default: I(false);
+ }
+
+ bool hasFork = false;
+
+ for (int i=lanes.size() - 1; i > idx; i--)
+ {
+ if (lastRevisions[i] == rev)
+ {
+ switch (defaultNextType)
+ {
+ case Revision::BarToLeft:
+ defaultNextType = Revision::BarToLeftCrossing;
+ break;
+ case Revision::Bar:
+ defaultNextType = Revision::Crossing;
+ break;
+ case Revision::BarToRight:
+ defaultNextType = Revision::BarToRightCrossing;
+ break;
+ default: I(false);
+ }
+
+ // do we join the line to a previous fork line?
+ if (!hasFork)
+ {
+ lanes[i] = Revision::BarToFork;
+ }
+ else
+ {
+ lanes[i] = Revision::BarToForkJoin;
+ }
+
+ // mark this line as not being continued
+ int deadLine = nextRevisions.indexOf(lastRevisions.at(i));
+ I(deadLine >= 0);
+ nextRevisions.replace(deadLine, QString());
+
+ hasFork = true;
+ continue;
+ }
+
+ lanes[i] = defaultNextType;
+ }
+
+ if (hasFork)
+ {
+ switch (type)
+ {
+ case Revision::Tail: type = Revision::TailFork; break;
+ case Revision::Node: type = Revision::Fork; break;
+ case Revision::Merge: type = Revision::MergeFork; break;
+ default: I(false);
+ }
+ }
+
+ lanes[idx] = type;
+ }
+
+ revObj->lanes = lanes;
+
+ if (newRevisions.size() == 0)
+ {
+ // FIXME: next thing is to draw the mess
+ emit lanesCreated();
+ return;
+ }
+
+ // recursion
+ buildLanes(newRevisions, nextRevisions);
+}
+
============================================================
--- src/model/Graph.h 12331956dd8457649a45dbfe59f4f10b48676d9d
+++ src/model/Graph.h 12331956dd8457649a45dbfe59f4f10b48676d9d
@@ -0,0 +1,117 @@
+/***************************************************************************
+ * Copyright (C) 2008 by Thomas Keller *
+ * address@hidden *
+ * *
+ * 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 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#ifndef REVISION_GRAPH
+#define REVISION_GRAPH
+
+#include "AutomateCommand.h"
+#include "vocab.h"
+
+class Revision
+{
+public:
+ QString hash;
+ QStringList parents;
+ QStringList children;
+ QStringList branches;
+
+ /**
+ * possible lane typess
+ */
+ enum LaneType { Empty, /* ( ) */
+ Bar, /* |
+ |
+ | */
+ Crossing, /* |
+ -^-
+ | */
+ BarToLeft, /* |
+ /
+ | */
+ BarToRight, /* |
+ \
+ | */
+ BarToLeftCrossing, /* |
+ -^--
+ | */
+ BarToRightCrossing, /* |
+ --^-
+ | */
+ BarToFork, /* |
+ _/
+ */
+ BarToForkJoin, /* |
+ _/--
+ */
+ SingleNode, /*
+ o
+ */
+ Node, /* |
+ o
+ | */
+ Fork, /* |
+ o-
+ | */
+ Merge, /* |
+ o
+ |\ */
+ MergeFork, /* |
+ o-
+ |\ */
+ Head, /*
+ o
+ | */
+ HeadMerge, /*
+ o
+ |\ */
+ Tail, /* |
+ o
+ */
+ TailFork, /* |
+ o-
+ */
+ };
+
+ QList lanes;
+};
+
+class Graph : public AutomateCommand
+{
+public:
+ Graph(const DatabaseFile &);
+ virtual ~Graph();
+
+ void readGraph();
+
+ void filterBranch(const QString &);
+
+signals:
+ void lanesCreated();
+
+private:
+ void processTaskResult(const MonotoneTask &);
+ void buildLanes(QStringList &, const QStringList &);
+
+ QMap revisionMap;
+ QMap branchHeads;
+ QString currentBranch;
+ DatabaseFile databaseFile;
+};
+
+#endif
+
============================================================
--- guitone.pro b38ecc510a2044518ac07a6c054bda668ede603f
+++ guitone.pro 30b599d7104bebd31fd40b59aea1eb04fd4cdfe1
@@ -83,6 +83,7 @@ HEADERS = src/view/TreeView.h \
src/model/GetRevision.h \
src/model/GetContentChanged.h \
src/model/GetDatabaseVariables.h \
+ src/model/Graph.h \
src/util/IconProvider.h \
src/util/AbstractParser.h \
src/util/BasicIOParser.h \
@@ -157,6 +158,7 @@ SOURCES += src/view/TreeView.cpp \
src/model/GetRevision.cpp \
src/model/GetContentChanged.cpp \
src/model/GetDatabaseVariables.cpp \
+ src/model/Graph.cpp \
src/util/IconProvider.cpp \
src/util/AbstractParser.cpp \
src/util/BasicIOParser.cpp \