# # # 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 \