Browse Source

First version with HMI propotype

- Added diff plane with file list
- Perform graphical effects
- Additional bugfixes
Alexey Edelev 7 years ago
parent
commit
49bd176d14

+ 3 - 20
commitgraph.cpp

@@ -13,6 +13,7 @@
 #include <git2/commit.h>
 
 CommitGraph::CommitGraph() : QObject()
+  ,m_branchesCount(0)
 {
     qsrand(QDateTime::currentMSecsSinceEpoch());
 }
@@ -55,10 +56,6 @@ void CommitGraph::addHead(const GitOid &oid)
         point->setY(m_sortedPoints.count() - i - 1);
         GitCommit *commit = GitCommit::fromOid(point->oid());
         git_commit* commitRaw = nullptr;
-        QPointer<GitTag> tag = commit->repository()->tags().value(commit->oid());
-        if(!tag.isNull()) {
-            point->setTag(tag.data()->name());
-        }
         int parentCount = git_commit_parentcount(commit->raw());
         delete commit;
 //        qDebug() << "New commit: " << point->oid().toString() << point->x() << point->y();
@@ -97,7 +94,9 @@ void CommitGraph::addHead(const GitOid &oid)
                 branchStarted.removeAll(point->x());
             }
         }
+        m_branchesCount = m_branchesCount < (point->x() + 1) ? (point->x() + 1) : m_branchesCount;
     }
+    emit branchesCountChanged(m_branchesCount);
 }
 
 void CommitGraph::findParents(GitCommit* commit)
@@ -160,19 +159,3 @@ void CommitGraph::addCommits(QList<GitOid>& reversList)
         }
     }
 }
-
-QString CommitGraph::commitData(GraphPoint *point) const
-{
-    if(point == nullptr) {
-        return QString();
-    }
-
-    QScopedPointer<GitCommit> commit(GitCommit::fromOid(point->oid()));
-    if(!commit.data()->isValid()) {
-        return QString();
-    }
-
-    return commit.data()->body();
-
-
-}

+ 11 - 3
commitgraph.h

@@ -12,11 +12,13 @@
 class GitCommit;
 class GraphPoint;
 class GitBranch;
+class GitDiff;
 
 class CommitGraph : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(QList<QObject*> points READ points CONSTANT)
+    Q_PROPERTY(int branchesCount READ branchesCount NOTIFY branchesCountChanged)
 public:
     CommitGraph();
     void addHead(GitBranch* branch);
@@ -26,9 +28,14 @@ public:
         return m_sortedPoints;
     }
 
-    //TODO: move this functionality to more proper place;
-    Q_INVOKABLE QString commitData(GraphPoint* point) const;
-    //TODO: move this functionality to more proper place;
+    int branchesCount() const
+    {
+        return m_branchesCount;
+    }
+
+signals:
+    void branchesCountChanged(int branchesCount);
+
 private:
     void findParents(GitCommit *commit);
     void addCommits(QList<GitOid> &reversList);
@@ -37,6 +44,7 @@ private:
 
     QHash<GitOid, GraphPoint*> m_points;
     QObjectList m_sortedPoints;
+    int m_branchesCount;
 };
 
 #endif // COMMITGRAPH_H

+ 19 - 0
commitmodel.cpp

@@ -3,6 +3,9 @@
 #include <gitbranch.h>
 #include <git2/revwalk.h>
 
+#include <commitgraph.h>
+#include <graphpoint.h>
+
 CommitModel::CommitModel(const QString &head, QObject* parent) : UniversalListModel(parent)
   ,m_head(head)
 {
@@ -32,3 +35,19 @@ CommitModel* CommitModel::fromBranch(GitBranch* branch)
     git_revwalk_free(walk);
     return tmpModel;
 }
+
+CommitModel* CommitModel::fromGraph(CommitGraph *graph)
+{
+    CommitModel* model = new CommitModel("HEAD");
+    QList<QObject*> points = graph->points();
+    for(int i = 0; i < points.count(); i++) {
+        GraphPoint* point = static_cast<GraphPoint*>(points.at(i));
+        model->m_container.prepend(GitCommit::fromOid(point->oid()));
+//        QPointer<GitTag> tag = commit->repository()->tags().value(commit->oid());
+//        if(!tag.isNull()) {
+//            point->setTag(tag.data()->name());
+//        }
+    }
+
+    return model;
+}

+ 3 - 0
commitmodel.h

@@ -6,6 +6,8 @@
 
 //GitBranch;
 
+class CommitGraph;
+
 class CommitModel : public UniversalListModel<GitCommit>
 {
     Q_OBJECT
@@ -14,6 +16,7 @@ public:
     CommitModel(const QString& head, QObject* parent = 0);
 
     static CommitModel* fromBranch(GitBranch* branch);
+    static CommitModel* fromGraph(CommitGraph* graph);
     QString head() const
     {
         return m_head;

BIN
fonts/Inconsolata.otf


+ 1 - 3
gitbranch.h

@@ -6,8 +6,6 @@
 
 struct git_oid;
 
-class CommitModel;
-
 class GitBranch : public GitReference
 {
     Q_OBJECT
@@ -27,6 +25,7 @@ public:
     QString name() const {
         return m_name;
     }
+
     BranchType type() const;
 
 public slots:
@@ -40,7 +39,6 @@ public slots:
 
 signals:
     void nameChanged(QString name);
-    void commitsChanged(CommitModel* commits);
 
 private:
     void free();

+ 20 - 30
gitcommit.cpp

@@ -1,6 +1,7 @@
 #include "gitcommit.h"
 
 #include <gitoid.h>
+#include <gitdiff.h>
 
 #include <QDebug>
 
@@ -11,6 +12,7 @@
 #include <git2/buffer.h>
 
 GitCommit::GitCommit(git_commit* raw, GitRepository* parent) : GitBase(raw, parent)
+  ,m_diff(nullptr)
 {
     m_oid = GitOid(git_commit_id(m_raw), m_repository);
 }
@@ -45,7 +47,7 @@ QString GitCommit::author() const
 
 QDateTime GitCommit::time() const
 {
-    return QDateTime::fromMSecsSinceEpoch(git_commit_time(m_raw));
+    return QDateTime::fromTime_t(git_commit_time(m_raw), Qt::OffsetFromUTC, git_commit_time_offset(m_raw));
 }
 
 QString GitCommit::message() const
@@ -73,42 +75,24 @@ bool GitCommit::isMerge() const
     return git_commit_parentcount(m_raw) > 1;
 }
 
-QString GitCommit::body()
+QString GitCommit::summary() const
+{
+    return QString(git_commit_summary(m_raw));
+}
+
+GitDiff* GitCommit::diff()
 {
     if(isMerge()) {
-        return QString("Commit - merge");
+        return nullptr;//QString("Commit - merge");
     }
 
-
-    if(m_body.isEmpty() || m_body.isNull()) {
+    if(m_diff.isNull()) {
         git_commit *parent = nullptr;
         git_commit_parent(&parent, raw(), 0);
-
-        git_tree *commit_tree = nullptr, *parent_tree = nullptr;
-        git_commit_tree(&commit_tree, raw());
-        git_commit_tree(&parent_tree, parent);
-
-        git_diff *diff = nullptr;
-        git_diff_tree_to_tree(
-                    &diff, repository()->raw()  , parent_tree, commit_tree, nullptr);
-
-
-       git_diff_print(diff,
-                      GIT_DIFF_FORMAT_PATCH,
-                        [](const git_diff_delta *delta, /**< delta that contains this data */
-                      const git_diff_hunk *hunk,   /**< hunk containing this data */
-                      const git_diff_line *line,   /**< line data */
-                      void *payload)->int
-        {
-           qDebug() << "GIT_DELTA_ADDED" << delta->status;
-//           qDebug() << hunk->new_lines;
-//           qDebug() << hunk->old_lines;
-//            qDebug() << line->new_lineno;
-            return 0;
-        }, this);
-//        m_body = QString::fromUtf8(git_diff_(raw()));
+        m_diff = new GitDiff(parent, raw(), repository());
+        git_commit_free(parent);
     }
-    return m_body;
+    return m_diff.data();
 }
 
 void GitCommit::setAuthor(QString author)
@@ -134,3 +118,9 @@ void GitCommit::setEmail(QString email)
     Q_UNUSED(email)
     //TODO
 }
+
+void GitCommit::setSummary(QString summary)
+{
+    Q_UNUSED(summary)
+    //TODO
+}

+ 9 - 3
gitcommit.h

@@ -10,6 +10,8 @@
 
 #include <git2/types.h>
 
+class GitDiff;
+
 class GitCommit : public GitBase<git_commit>
 {
     Q_OBJECT
@@ -17,10 +19,11 @@ class GitCommit : public GitBase<git_commit>
     Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY commitChanged)
     Q_PROPERTY(QDateTime time READ time WRITE setTime NOTIFY commitChanged)
     Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY commitChanged)
+    Q_PROPERTY(QString summary READ summary WRITE setSummary NOTIFY commitChanged)
     Q_PROPERTY(QString sha1 READ sha1 NOTIFY commitChanged)
     Q_PROPERTY(QString shortSha1 READ shortSha1 NOTIFY commitChanged)
     Q_PROPERTY(bool isMerge READ isMerge NOTIFY commitChanged)
-    Q_PROPERTY(QString body READ body NOTIFY bodyChanged)
+    Q_PROPERTY(GitDiff* diff READ diff NOTIFY commitChanged)
 
 public:
     GitCommit(git_commit* raw, GitRepository* parent);
@@ -35,13 +38,15 @@ public:
     QString sha1() const;
     QString shortSha1() const;
     bool isMerge() const;
-    QString body();
+    QString summary() const;
+    GitDiff* diff();
 
 public slots:
     void setAuthor(QString author);
     void setTime(QDateTime time);
     void setMessage(QString message);
     void setEmail(QString email);
+    void setSummary(QString summary);
 
 signals:
     void commitChanged();
@@ -55,7 +60,8 @@ private:
     QDateTime m_time;
     QString m_message;
     QString m_email;
-    QString m_body;
+    QPointer<GitDiff> m_diff;
+    QString m_summary;
 };
 
 #endif // GITCOMMIT_H

+ 89 - 1
gitdiff.cpp

@@ -1,6 +1,94 @@
 #include "gitdiff.h"
 
-GitDiff::GitDiff()
+#include <gitrepository.h>
+
+#include <QDebug>
+
+#include <git2/types.h>
+#include <git2/diff.h>
+#include <git2/commit.h>
+
+GitDiff::GitDiff(git_commit* a, git_commit* b, GitRepository *repository) : QObject()
+  ,m_repository(repository)
 {
+    Q_ASSERT_X(m_repository, "GitDiff", "Repository of NULL");
+    connect(m_repository, &GitRepository::destroyed, this, &GitDiff::deleteLater);
+    readBody(a, b);
+}
+
+void GitDiff::readBody(git_commit *a, git_commit *b)
+{
+    git_diff *diff = nullptr;
+
+    git_tree *a_tree = nullptr;
+    git_tree *b_tree = nullptr;
+
+
+    git_commit_tree(&a_tree, a);
+    git_commit_tree(&b_tree, b);
+
+    git_diff_tree_to_tree(&diff, m_repository->raw(), a_tree, b_tree, nullptr);
+
+
+    git_diff_print(diff,
+                   GIT_DIFF_FORMAT_PATCH,
+                   [](const git_diff_delta *delta, const git_diff_hunk *hunk,
+                   const git_diff_line *line, void *payload)
+    {
+        Q_UNUSED(hunk)
+
+        QString prefix("<font color=\"%1\">%2");
+        QString suffix("</font><br/>");
+        GitDiff* diff = static_cast<GitDiff*>(payload);
+        QString fileName(delta->new_file.path);
+        if(line->origin == GIT_DIFF_LINE_FILE_HDR) {
+            return 0;
+        }
 
+        switch(line->origin) {
+        case GIT_DIFF_LINE_ADDITION:
+            prefix = prefix.arg("#00ff00");
+            break;
+        case GIT_DIFF_LINE_DELETION:
+            prefix = prefix.arg("#ff0000");
+            break;
+        case GIT_DIFF_LINE_HUNK_HDR:
+            prefix = "<br/><b>";
+            suffix = "</b><br/>";
+//            prefix = prefix.arg("#000000").arg("");
+            break;
+        default:
+            prefix = prefix.arg("#000000").arg("&nbsp;");
+            break;
+        }
+
+        if ( line->origin == GIT_DIFF_LINE_ADDITION ||
+                line->origin == GIT_DIFF_LINE_DELETION) {
+            prefix = prefix.arg(line->origin);
+        }
+
+        diff->m_diffList[fileName].append(prefix);
+        diff->m_diffList[fileName].append(QString::fromUtf8(line->content, line->content_len).toHtmlEscaped().replace(" ", "&nbsp;"));
+        diff->m_diffList[fileName].append(suffix);
+        return 0;
+    }, this);
+
+    git_diff_free(diff);
+    git_tree_free(a_tree);
+    git_tree_free(b_tree);
+}
+
+void GitDiff::reset()
+{
+    m_diffList.clear();
+}
+
+QStringList GitDiff::files()
+{
+    return m_diffList.keys();
+}
+
+QString GitDiff::unified(const QString& file)
+{
+    return m_diffList.value(file);
 }

+ 24 - 3
gitdiff.h

@@ -1,11 +1,32 @@
 #ifndef GITDIFF_H
 #define GITDIFF_H
 
+#include <QObject>
+#include <QHash>
+#include <QString>
 
-class GitDiff
+struct git_commit;
+
+class GitRepository;
+
+class GitDiff : public QObject
 {
+    Q_OBJECT
+    Q_PROPERTY(QStringList files READ files CONSTANT)
+
 public:
-    GitDiff();
+    GitDiff(git_commit* a, git_commit* b, GitRepository* repository);
+    ~GitDiff(){}
+    void readBody(git_commit* a, git_commit* b);
+    void reset();
+
+    Q_INVOKABLE QString unified(const QString& file);
+
+    QStringList files();
+
+private:
+    QHash<QString, QString> m_diffList;
+    GitRepository* m_repository;
 };
 
-#endif // GITDIFF_H
+#endif // GITDIFF_H

+ 33 - 10
githandler.cpp

@@ -6,6 +6,7 @@
 
 #include <gitrepository.h>
 #include <gitbranch.h>
+#include <gitdiff.h>
 #include <commitmodel.h>
 #include <tagmodel.h>
 #include <git2.h>
@@ -15,6 +16,7 @@
 
 GitHandler::GitHandler() : QObject()
   ,m_repositories(new RepositoryModel(this))
+  ,m_activeDiff(nullptr)
 {
     git_libgit2_init();
 }
@@ -39,13 +41,13 @@ void GitHandler::open(const QString &path)
         return;
     }
 
-    GitRepository* repo = new GitRepository(QString::fromUtf8(root.ptr, root.size));
-    if(!repo->isValid()) {
+    m_activeRepo = new GitRepository(QString::fromUtf8(root.ptr, root.size));
+    if(!m_activeRepo->isValid()) {
         qDebug() << lastError();
         return;
     }
 
-    BranchContainer &branches = repo->branches();
+    BranchContainer &branches = m_activeRepo->branches();
 
     CommitGraph* graph = new CommitGraph();
     graph->addHead(branches.value("master").data()->oid());
@@ -55,15 +57,10 @@ void GitHandler::open(const QString &path)
     }
 
     setGraph(graph);
-    m_repositories->add(repo);
+    m_commits = CommitModel::fromGraph(graph);
+    m_repositories->add(m_activeRepo);
 }
 
-CommitModel* GitHandler::modelByHead(const QString& head)
-{
-    return m_commits.value(head).data();
-}
-
-
 QString GitHandler::lastError() const
 {
     const git_error *e = giterr_last();
@@ -74,3 +71,29 @@ QString GitHandler::lastError() const
     giterr_clear();
     return QString();
 }
+
+GitDiff* GitHandler::diff(GitCommit* a, GitCommit* b)
+{
+    if(!m_activeDiff.isNull()) {
+        m_activeDiff->deleteLater();
+    }
+
+    Q_ASSERT_X(a->repository() == b->repository(), "GitHandler", "Cross repository diff requested");
+
+    m_activeDiff = new GitDiff(a->raw(), b->raw(), a->repository());
+    return m_activeDiff.data();
+}
+
+void GitHandler::setActiveDiff(GitDiff* activeDiff)
+{
+    if (m_activeDiff == activeDiff)
+        return;
+
+    m_activeDiff = activeDiff;
+    emit activeDiffChanged(activeDiff);
+}
+
+GitDiff* GitHandler::activeDiff() const
+{
+    return m_activeDiff.data();
+}

+ 35 - 3
githandler.h

@@ -15,6 +15,9 @@ class GitHandler : public QObject
     Q_OBJECT
     Q_PROPERTY(RepositoryModel* repositories READ repositories NOTIFY repositoriesChanged)
     Q_PROPERTY(CommitGraph* graph READ graph WRITE setGraph NOTIFY graphChanged)
+    Q_PROPERTY(GitRepository* activeRepo READ activeRepo CONSTANT)
+    Q_PROPERTY(GitDiff* activeDiff READ activeDiff WRITE setActiveDiff NOTIFY activeDiffChanged)
+    Q_PROPERTY(CommitModel* commits READ commits WRITE setCommits NOTIFY commitsChanged)
 
 public:
     GitHandler();
@@ -22,18 +25,30 @@ public:
     Q_INVOKABLE void open(const QString &path);
     Q_INVOKABLE void open(const QUrl &url);
 
+    Q_INVOKABLE GitDiff* diff(GitCommit* a, GitCommit* b);
+
     RepositoryModel* repositories() const
     {
         return m_repositories;
     }
 
-    Q_INVOKABLE CommitModel* modelByHead(const QString& head);
-
     CommitGraph* graph() const
     {
         return m_graph;
     }
 
+    GitRepository* activeRepo() const
+    {
+        return m_activeRepo;
+    }
+
+    GitDiff* activeDiff() const;
+
+    CommitModel* commits() const
+    {
+        return m_commits;
+    }
+
 public slots:
     void setGraph(CommitGraph* graph)
     {
@@ -44,18 +59,35 @@ public slots:
         emit graphChanged(graph);
     }
 
+    void setActiveDiff(GitDiff* activeDiff);
+
+    void setCommits(CommitModel* commits)
+    {
+        if (m_commits == commits)
+            return;
+
+        m_commits = commits;
+        emit commitsChanged(commits);
+    }
+
 signals:
     void repositoriesChanged(RepositoryModel* repositories);
 
     void graphChanged(CommitGraph* graph);
 
+    void activeDiffChanged(GitDiff* activeDiff);
+
+    void commitsChanged(CommitModel* commits);
+
 protected:
     QString lastError() const;
 
 private:
     RepositoryModel* m_repositories;
-    CommitModelContainer m_commits;
+    CommitModel* m_commits;
     CommitGraph* m_graph;
+    GitRepository* m_activeRepo;
+    QPointer<GitDiff> m_activeDiff;
 };
 
 #endif // GITHANDLER_H

+ 2 - 3
gitrepository.cpp

@@ -7,6 +7,7 @@
 #include <gitbranch.h>
 #include <gitcommit.h>
 #include <gittag.h>
+#include <gitdiff.h>
 
 #include <git2.h>
 
@@ -56,8 +57,7 @@ void GitRepository::readBranches()
 
 void GitRepository::readTags()
 {
-    git_tag_foreach(
-                raw(),
+    git_tag_foreach(raw(),
                 [](const char *name, git_oid *oid, void *payload) -> int
     {
         Q_UNUSED(payload)
@@ -78,4 +78,3 @@ void GitRepository::readTags()
     },
     this);
 }
-

+ 2 - 1
gitrepository.h

@@ -12,6 +12,8 @@ struct git_repository;
 
 class GitBranch;
 class GitTag;
+class GitDiff;
+class GitCommit;
 struct git_oid;
 
 typedef QMap<QString, QPointer<GitBranch> > BranchContainer;
@@ -56,7 +58,6 @@ public:
         return m_tags;
     }
 
-
 public slots:
     void setRoot(QString root) {
         if (m_root == root)

+ 2 - 2
graphpoint.h

@@ -51,8 +51,6 @@ public:
         return m_childPoints.count();
     }
 
-    bool addChildPoint(GraphPoint* point);
-
     QList<QObject*> childPoints() const
     {
         return m_childPoints;
@@ -68,6 +66,8 @@ public:
         return m_branch;
     }
 
+    bool addChildPoint(GraphPoint* point);
+
 public slots:
     void setX(int x);
     void setY(int y);

BIN
images/arrow-141-16.png


BIN
images/arrow-141-16_active.png


BIN
images/arrow-141-24.png


BIN
images/arrow-141-24_active.png


BIN
images/arrow-203-16.png


BIN
images/arrow-203-16_active.png


BIN
images/arrow-204-16.png


BIN
images/arrow-204-16_active.png


BIN
images/arrow-204-24.png


BIN
images/arrow-204-24_active.png


BIN
images/x-mark-3-24.png


BIN
images/x-mark-3-24_active.png


BIN
images/x-mark-3-48.png


BIN
images/x-mark-3-48_active.png


+ 6 - 2
main.cpp

@@ -2,6 +2,7 @@
 #include <QQmlEngine>
 #include <QQuickView>
 #include <QQmlContext>
+#include <QFontDatabase>
 
 #include <githandler.h>
 #include <gitrepository.h>
@@ -11,13 +12,14 @@
 #include <graphpoint.h>
 #include <commitgraph.h>
 #include <gittag.h>
+#include <gitdiff.h>
 
 #include <QDebug>
 
 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
-
+    QFontDatabase::addApplicationFont(":/fonts/Inconsolata.otf");
     QQuickView view;
 
     qmlRegisterUncreatableType<CommitModel>("org.semlanik.nicegit", 1, 0, "CommitModel", "Owned only by GitHandler");
@@ -25,9 +27,11 @@ int main(int argc, char *argv[])
     qmlRegisterUncreatableType<GraphPoint>("org.semlanik.nicegit", 1, 0, "GraphPoint", "Owned only by GitHandler");
     qmlRegisterUncreatableType<RepositoryModel>("org.semlanik.nicegit", 1, 0, "RepositoryModel", "Owned only by GitHandler");
     qmlRegisterUncreatableType<GitRepository>("org.semlanik.nicegit", 1, 0, "GitRepository", "Owned only by GitHandler");
-    qmlRegisterUncreatableType<GitBranch>("org.semlanik.nicegit", 1, 0, "GitRepository", "Owned only by GitHandler");
+    qmlRegisterUncreatableType<GitBranch>("org.semlanik.nicegit", 1, 0, "GitBranch", "Owned only by GitHandler");
     qmlRegisterUncreatableType<GitHandler>("org.semlanik.nicegit", 1, 0, "GitHandler", "Global for qml");
     qmlRegisterUncreatableType<GitTag>("org.semlanik.nicegit", 1, 0, "GitTag", "Global for qml");
+    qmlRegisterUncreatableType<GitDiff>("org.semlanik.nicegit", 1, 0, "GitDiff", "Global for qml");
+    qmlRegisterUncreatableType<GitCommit>("org.semlanik.nicegit", 1, 0, "GitCommit", "Global for qml");
 
 
     GitHandler handler;

+ 21 - 0
qml/AreaPlaceholder.qml

@@ -0,0 +1,21 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    property bool active: false
+    property alias text: phText.text
+    anchors.fill: parent
+    opacity: active ? 1.0 : 0.0
+    visible: opacity > 0
+    Behavior on opacity {
+        NumberAnimation {
+            duration: 200
+        }
+    }
+    Text {
+        id: phText
+        anchors.centerIn: parent
+        color: "#bbbbbb"
+        font.pointSize: 14
+    }
+}

+ 86 - 0
qml/CommitInfo.qml

@@ -0,0 +1,86 @@
+import QtQuick 2.0
+
+Item {
+    id: root
+    state: "closed"
+    clip: true
+    states: [
+        State {
+            name: "opened"
+            PropertyChanges {
+                target: root
+                height: childrenRect.height
+            }
+        },
+        State {
+            name: "closed"
+            PropertyChanges {
+                target: root
+                height: shortInfo.height
+            }
+        }
+    ]
+
+    transitions: Transition {
+        NumberAnimation {
+            properties: "height"
+            duration: 200
+        }
+    }
+    Column {
+        id: shortInfo
+        anchors.top: parent.top
+        width: parent.width
+        CommitInfoLine {
+            field: qsTr("Time")
+            value: commit ? commit.time : ""
+        }
+
+        CommitInfoLine {
+            field: qsTr("Autor")
+            value: commit ? commit.author : ""
+        }
+
+        CommitInfoLine {
+            field: qsTr("Email")
+            value: commit ? commit.email : ""
+        }
+    }
+    Column {
+        anchors.top: shortInfo.bottom
+        width: parent.width
+        Text {
+            wrapMode: Text.WordWrap
+            width: contentWidth
+            height: contentHeight
+            text: qsTr("Message") + ": "
+            font.pointSize: 10
+            font.weight: Font.Bold
+        }
+
+        Rectangle {
+            width: 10
+            height: 10
+        }
+
+        Text {
+            id: content
+            wrapMode: Text.WordWrap
+            width: parent.width
+            height: contentHeight
+            text: commit ? commit.message : ""
+        }
+
+        Rectangle {
+            anchors.horizontalCenter: parent.horizontalCenter
+            width: parent.width - 4
+            height: 2
+            radius: 2
+            color: "#eeeeee"
+        }
+        Rectangle {
+            width: 10
+            height: 10
+        }
+    }
+}

+ 41 - 0
qml/CommitInfoLine.qml

@@ -0,0 +1,41 @@
+import QtQuick 2.0
+
+Item {
+    id: root
+    property string field: ""
+    property string value: ""
+
+    width: parent.width
+    height:childrenRect.height + 10
+    clip: true
+    Row {
+        id: info
+
+        height: content.height
+        Text {
+            wrapMode: Text.WordWrap
+            width: contentWidth
+            height: contentHeight
+            text: qsTr(root.field) + ": "
+            font.pointSize: 10
+            font.weight: Font.Bold
+        }
+        Text {
+            id: content
+            width: contentWidth
+            height: contentHeight
+            font.pointSize: 10
+            text: root.value
+        }
+    }
+
+    Rectangle {
+        anchors.top: info.bottom
+        anchors.topMargin: 10
+        anchors.horizontalCenter: parent.horizontalCenter
+        width: parent.width - 4
+        height: 2
+        radius: 2
+        color: "#eeeeee"
+    }
+}

+ 129 - 0
qml/CommitList.qml

@@ -0,0 +1,129 @@
+import QtQuick 2.0
+
+FlickPager {
+    id: root
+    property QtObject graphModel: null
+    property QtObject commitsModel: null
+    signal commitClicked(var commit)
+
+    QtObject {
+        id: d
+        property int commitsWidth: 0
+        property int graphWidth: 0
+        property int fullMessageWidth: 600
+    }
+
+    width: 120
+    clip: true
+    state: "full"
+    states: [
+        State {
+            name: "full"
+            PropertyChanges {
+                target: root
+                width: d.fullMessageWidth + d.commitsWidth + d.graphWidth
+            }
+        },
+        State {
+            name: "commitsOnly"
+            PropertyChanges {
+                target: root
+                width: d.commitsWidth + d.graphWidth
+            }
+        },
+        State {
+            name: "treeOnly"
+            PropertyChanges {
+                target: root
+                width: 120
+            }
+        }
+    ]
+
+    transitions: [
+        Transition {
+            NumberAnimation {
+                properties: "width"
+                duration: 200
+            }
+        }
+    ]
+
+    content: Item {
+        id: innerItem
+        width: root.width
+        height: graph.height
+
+        Column {
+            anchors.left: parent.left
+            anchors.right: parent.right
+            clip: true
+            spacing: graph.spacingV
+            Repeater {
+                model: root.commitsModel
+                Rectangle {
+                    width: parent.width
+                    height: graph.elementHeight
+                    color: textSelector.containsMouse ? "#bbbbbb" : "#00bbbbbb"
+                    Item {
+                        width: root.width - graph.width
+                        height: sha1.height
+                        anchors.right: parent.right
+                        Text {
+                            id: sha1
+                            font.family: "Inconsolata"
+                            font.pointSize: 12
+                            anchors.left: parent.left
+                            text: "[" + model.shortSha1 + "]"
+                            Component.onCompleted: {
+                                d.commitsWidth = sha1.width  + 10
+                            }
+                        }
+
+                        Text {
+                            anchors.left: sha1.right
+                            anchors.right: parent.right
+                            anchors.leftMargin: 10
+                            anchors.rightMargin: 10
+                            verticalAlignment: Text.AlignVCenter
+                            text: model.summary
+                            elide: Text.ElideRight
+                        }
+                    }
+                    //TODO: Make as part of graph
+                    //                    Row {
+                    //                        anchors.left: parent.left
+                    //                        Text {
+                    //                            verticalAlignment: Text.AlignVCenter
+                    //                            horizontalAlignment: Text.AlignLeft
+                    //                            text: graphPointData.branch
+                    //                        }
+                    //                        Text {
+                    //                            verticalAlignment: Text.AlignVCenter
+                    //                            horizontalAlignment: Text.AlignLeft
+                    //                            text: graphPointData.tag
+                    //                        }
+                    //                    }
+
+                    MouseArea {
+                        id: textSelector
+                        anchors.fill: parent
+                        hoverEnabled: true
+                        focus: true
+                        onClicked: {
+                            root.commitClicked(model.modelData)
+                        }
+                    }
+                }
+            }
+        }
+
+        Graph {
+            id: graph
+            model: root.graphModel
+            onWidthChanged: {
+                d.graphWidth = graph.width
+            }
+        }
+    }
+}

+ 128 - 0
qml/CommitPlane.qml

@@ -0,0 +1,128 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.4
+
+Item {
+    id: root
+    property QtObject commit: null
+    property QtObject diff: null
+
+    onCommitChanged: {
+        if(commit != null) {
+            diff = commit.diff
+        } else {
+            diff = null
+        }
+    }
+
+    QtObject {
+        id: d
+        property int viewportMargins: 20
+        property int commitInfoWidth: 400
+    }
+
+    CommitInfo {
+        id: commitInfo
+        anchors.top: parent.top
+        anchors.left: parent.left
+        anchors.leftMargin: 20
+        width: d.commitInfoWidth
+    }
+
+    DiffFiles {
+        id: files
+        files: diff ? diff.files : null
+        anchors.top: arrow.bottom
+        anchors.topMargin: -10
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.leftMargin: 20
+        width: d.commitInfoWidth
+        onOpenDiff: {
+            fileDiff.text = diff.unified(file)
+        }
+    }
+
+    FlickPagerArrow {
+        id: arrow
+        anchors.top: commitInfo.bottom
+        source: commitInfo.state === "opened" ? "arrow-141-16" : "arrow-203-16"
+        active: true
+        anchors.left: commitInfo.left
+        anchors.right: commitInfo.right
+        onClicked: {
+            commitInfo.state = commitInfo.state === "opened" ? "closed" : "opened"
+        }
+        gradient: Gradient {
+            GradientStop { position: 0.0; color: "#00ffffff" }
+            GradientStop { position: 0.2; color: "#ffffffff" }
+            GradientStop { position: 0.8; color: "#ffffffff" }
+            GradientStop { position: 1.0; color: "#00ffffff" }
+        }
+    }
+
+    ScrollView {
+        id: diffViewport
+        anchors.top: topDecor.bottom
+        anchors.bottom: bottomDecor.top
+        anchors.left: files.right
+        anchors.leftMargin: 20
+        anchors.right: parent.right
+        anchors.topMargin: -10
+        anchors.bottomMargin: -10
+        __wheelAreaScrollSpeed:30
+        clip: true
+        Item {
+            id: innerItem
+            width: fileDiff.contentWidth
+            height: fileDiff.contentHeight + 20
+            Text {
+                id: fileDiff
+                anchors.top: parent.top
+                anchors.topMargin: 10
+                font.family: "Inconsolata"
+                font.pointSize: 12
+            }
+        }
+    }
+
+    onDiffChanged: {
+        fileDiff.text = ""
+    }
+
+    Rectangle {
+        id: topDecor
+        anchors.right: diffViewport.right
+        anchors.left: diffViewport.left
+        anchors.top: parent.top
+        height: d.viewportMargins
+        gradient: Gradient {
+            GradientStop { position: 0.0; color: "#ffffffff" }
+            GradientStop { position: 0.7; color: "#ffffffff" }
+            GradientStop { position: 1.0; color: "#00ffffff" }
+        }
+    }
+    Rectangle {
+        id: bottomDecor
+        anchors.right: diffViewport.right
+        anchors.left: diffViewport.left
+        anchors.bottom: parent.bottom
+        height: d.viewportMargins
+        gradient: Gradient {
+            GradientStop { position: 0.0; color: "#00ffffff" }
+            GradientStop { position: 0.3; color: "#ffffffff" }
+            GradientStop { position: 1.0; color: "#ffffffff" }
+        }
+    }
+
+    AreaPlaceholder {
+        anchors.fill: diffViewport
+        active: fileDiff.text == ""
+        visible: diff != null
+        text: "Select file on side panel"
+    }
+
+    AreaPlaceholder {
+        active: diff == null
+        text: "Select commit in graph"
+    }
+}

+ 32 - 0
qml/DiffFiles.qml

@@ -0,0 +1,32 @@
+import QtQuick 2.0
+
+ListView {
+    id: root
+    property var files: null
+    signal openDiff(var file)
+    spacing: 10
+    model: files ? files : 0
+    clip: true
+    delegate: Item {
+        width: commitBodyText.width
+        height: commitBodyText.height + 10
+        Text {
+            id: commitBodyText
+            anchors.bottom: parent.bottom
+            font.pointSize: 10
+            width: root.width
+            text: modelData
+            elide: Text.ElideLeft
+            horizontalAlignment: Text.AlignRight
+            color:control.containsMouse ? "#aaaaaa" : "#000000"
+            MouseArea {
+                id: control
+                anchors.fill: parent
+                hoverEnabled: true
+                onClicked: {
+                    root.openDiff(modelData)
+                }
+            }
+        }
+    }
+}

+ 60 - 0
qml/FlickPager.qml

@@ -0,0 +1,60 @@
+import QtQuick 2.0
+
+Item {
+    id: root
+    property Component content: null
+    QtObject {
+        id: d
+        property Item flickItem: null
+    }
+
+    Flickable {
+        id: flick
+        anchors.top: arrowUp.bottom
+        anchors.bottom:arrowDown.top
+        anchors.left: parent.left
+        anchors.right: parent.right
+        contentWidth: d.flickItem.width
+        contentHeight: d.flickItem.height
+    }
+
+    FlickPagerArrow {
+        id: arrowUp
+        anchors.top: parent.top
+        source: "arrow-141-16"
+        active: flick.contentY > 0
+        onClicked: {
+            flick.contentY -= (flick.contentY - flick.height) < 0 ? flick.contentY : flick.height
+        }
+        gradient: Gradient {
+            GradientStop { position: 0.0; color: "#ffffffff" }
+            GradientStop { position: 0.8; color: "#ffffffff" }
+            GradientStop { position: 1.0; color: "#00ffffff" }
+        }
+    }
+
+    FlickPagerArrow {
+        id: arrowDown
+        anchors.bottom: parent.bottom
+        source: "arrow-203-16"
+        active: flick.contentY < (flick.contentHeight - flick.height)
+        onClicked: {
+            flick.contentY += (flick.contentY + flick.height*2) >= flick.contentHeight ? flick.contentHeight - flick.contentY - flick.height : flick.height
+        }
+        gradient: Gradient {
+            GradientStop { position: 0.0; color: "#00ffffff" }
+            GradientStop { position: 0.2; color: "#ffffffff" }
+            GradientStop { position: 1.0; color: "#ffffffff" }
+        }
+    }
+
+    onContentChanged: {
+        if(d.flickItem != null) {
+            d.flickItem.destroy()
+        }
+
+        if(content != null) {
+            d.flickItem = content.createObject(flick.contentItem)
+        }
+    }
+}

+ 33 - 0
qml/FlickPagerArrow.qml

@@ -0,0 +1,33 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: arrow
+    property string source: icon.source
+    property bool active: false
+    signal clicked()
+
+    anchors.right: parent.right
+    anchors.left: parent.left
+    height: 27
+    Image {
+        id: icon
+        Behavior on opacity {
+            NumberAnimation {
+                duration: 200
+            }
+        }
+        visible: opacity > 0.0
+        opacity: active ? 1.0 : 0.0
+        anchors.centerIn: parent
+        source: control.pressed ? "qrc:///images/" + arrow.source + "_active.png" : "qrc:///images/" + arrow.source + ".png"
+    }
+
+    MouseArea {
+        id: control
+        anchors.fill: parent
+        hoverEnabled: true
+        onClicked: {
+            arrow.clicked()
+        }
+    }
+}

+ 67 - 0
qml/Graph.qml

@@ -0,0 +1,67 @@
+import QtQuick 2.0
+
+Canvas {
+    id: root
+    property int elementWidth: 20
+    property int elementHeight: 20
+    property int spacingH: 12
+    property int spacingV: 20
+    property int lineWidth: 1
+    property QtObject model: null
+    height: model.points.length*(elementWidth + spacingV)
+    width: (elementWidth + spacingH)*model.branchesCount
+    QtObject {
+        id: d
+        property int halfElementWidth: elementWidth/2
+        property int halfElementHeight: elementHeight/2
+    }
+
+    onPaint: {
+        var ctx = getContext("2d")
+        for(var i = 0; i < model.points.length; i++) {
+            var point = model.points[i]
+            var pointAbsX = point.x*(elementWidth + spacingH)
+            var pointAbsY = point.y*(elementHeight + spacingV)
+            var childPoints = point.childPoints
+
+            ctx.beginPath()
+            ctx.lineWidth = root.lineWidth
+            ctx.strokeStyle = "#"+point.color
+            ctx.roundedRect(pointAbsX, pointAbsY, elementWidth, elementHeight, elementWidth, elementHeight)
+            ctx.stroke()
+            ctx.closePath()
+
+            for(var j = 0; j < childPoints.length; j++) {
+                var childPoint = childPoints[j]
+                var childPointAbsX = childPoint.x*(elementWidth + spacingH)
+                var childPointAbsY = childPoint.y*(elementHeight + spacingV)
+                ctx.beginPath()
+                ctx.strokeStyle = "#" + childPoint.color
+                ctx.lineWidth = root.lineWidth
+                if(point.x !== childPoint.x) {
+                    if(point.x < childPoint.x) {
+                        ctx.moveTo(pointAbsX + d.halfElementWidth, childPointAbsY + elementHeight + spacingV)
+                        ctx.bezierCurveTo(
+                                    pointAbsX + d.halfElementWidth, childPointAbsY + elementHeight,
+                                    childPointAbsX + d.halfElementWidth, childPointAbsY + elementHeight + spacingV/* + d.halfElementHeight*/,
+                                    childPointAbsX + d.halfElementWidth, childPointAbsY + elementHeight/*d.halfElementHeight*/)
+                    } else {
+                        ctx.moveTo(pointAbsX + d.halfElementWidth, pointAbsY/* + d.halfElementHeight*/)
+                        ctx.lineTo(pointAbsX + d.halfElementWidth, childPointAbsY + elementHeight + spacingV + elementHeight)
+                        ctx.bezierCurveTo(
+                                    pointAbsX + d.halfElementWidth, childPointAbsY + d.halfElementHeight,
+                                    childPointAbsX + d.halfElementWidth, childPointAbsY + elementHeight + spacingV/*  + d.halfElementHeight*/,
+                                    childPointAbsX + d.halfElementWidth, childPointAbsY + elementHeight/*d.halfElementHeight*/)
+                        //For debuggin bazier curve migh be replaced buy straight line
+                        //                                ctx.lineTo(childPointAbsX + d.halfElementWidth, childPointAbsY + d.halfElementHeight)
+                    }
+                } else {
+                    ctx.moveTo(pointAbsX + d.halfElementWidth, pointAbsY/* + d.halfElementHeight*/)
+                    ctx.lineTo(childPointAbsX + d.halfElementWidth, childPointAbsY + elementHeight)
+                }
+                ctx.stroke()
+                ctx.closePath()
+            }
+        }
+    }
+}

+ 58 - 135
qml/MainView.qml

@@ -1,162 +1,85 @@
 import QtQuick 2.0
 import QtQuick.Controls 1.4
-import QtQuick.Dialogs 1.2
 import org.semlanik.nicegit 1.0
 
 Item {
     id: root
-    Row {
-        id: selector
-        Text {
-            text: "Active repository" + repoOpenDialog.fileUrl
-        }
-        Button {
-            text: "Choose..."
-            onClicked: repoOpenDialog.open()
+    property var commitsForDiff: null
+    property bool controlActive: false
+    TopBar {
+        id: topBar
+        onCloseClicked: {
+            commitPlane.diff = null
+            commitPlane.commit = null
+            commitList.state = "full"
         }
+        closeVisible: commitPlane.diff != null
     }
 
-    Column {
-        anchors.top: selector.bottom
-        Repeater {
-            model: _handler.repositories
-
-            Text {
-                text: model.name
-            }
-        }
+    Rectangle {
+        id: bg
+        color: "#eeeeee"
+        anchors.fill: commitList
     }
 
-    Flickable {
-        width: root.width/2
-        anchors.top: parent.top
+    CommitList {
+        id: commitList
+        anchors.top: topBar.bottom
         anchors.bottom: parent.bottom
-        anchors.right: parent.right
-        contentWidth: innerItem.width
-        contentHeight: innerItem.height
-        Canvas {
-            id: innerItem
-            width: root.width/2
-            property int elementWidth: 20
-            property int elementHeight: 20
-            height: _handler.graph.points.length*(elementWidth + 20)
-            onPaint: {
-                var ctx = getContext("2d")
-                for(var i = 0; i < _handler.graph.points.length; i++) {
-                    var point = _handler.graph.points[i]
-
-                    ctx.beginPath()
-                    ctx.fillStyle = "#"+point.color
-                    ctx.roundedRect(point.x*(elementWidth + 20), point.y*(elementHeight + 20), elementWidth, elementHeight, elementWidth, elementHeight)
-                    ctx.fill()
-                    ctx.closePath()
+        anchors.left: parent.left
 
-                    var childPoints = point.childPoints
+        commitsModel: _handler.commits
+        graphModel: _handler.graph
+        onCommitClicked: {
+            if(commit.diff == null) {
+                commitPlane.commit = null
+                commitPlane.diff = null
+                commitList.state = "full"
+                return
+            }
 
-                    for(var j = 0; j < childPoints.length; j++) {
-                        var childPoint = childPoints[j]
-                        ctx.beginPath()
-                        ctx.strokeStyle = "#" + childPoint.color
-                        ctx.lineWidth = 2
-                        if(point.x !== childPoint.x) {
-                            if(point.x < childPoint.x) {
-                                ctx.moveTo(point.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight + 20)
-                                ctx.bezierCurveTo(
-                                                  point.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight,
-                                                  childPoint.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight + 20 + elementHeight/2,
-                                                  childPoint.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight/2)
-                            } else {
-                                ctx.moveTo(point.x*(elementWidth + 20) + elementWidth/2, point.y*(elementHeight + 20) + elementHeight/2)
-                                ctx.lineTo(point.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight + 20 + elementHeight)
-                                ctx.bezierCurveTo(
-                                                  point.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight/2,
-                                                  childPoint.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight + 20  + elementHeight/2,
-                                                  childPoint.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight/2)
-//                                ctx.lineTo(childPoint.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight/2)
-                            }
-                        } else {
-                            ctx.moveTo(point.x*(elementWidth + 20) + elementWidth/2, point.y*(elementHeight + 20) + elementHeight/2)
-                            ctx.lineTo(childPoint.x*(elementWidth + 20) + elementWidth/2, childPoint.y*(elementHeight + 20) + elementHeight/2)
-                        }
-                        ctx.stroke()
-                        ctx.closePath()
-                    }
+            commitList.state = "commitsOnly"
+            if(!root.controlActive) {
+                commitPlane.commit = commit
+                root.commitsForDiff=[]
+            } else {
+                console.log("root.controlActive: " + root.controlActive)
+                if(root.commitsForDiff === null) {
+                    root.commitsForDiff = new Array(0);
                 }
-            }
-        }
-        Column {
-            width: parent.width
-            spacing: 20
-            Repeater {
-                model: _handler.graph.points.length
-                Rectangle {
-                    width: parent.width
-                    height: innerItem.elementHeight
-                    color: textSelector.containsMouse ? "#9999ff" : "#009999ff"
-                    property QtObject graphPointData: _handler.graph.points[_handler.graph.points.length - model.index - 1]
-                    Text {
-                        anchors.fill: parent
-                        verticalAlignment: Text.AlignVCenter
-                        horizontalAlignment: Text.AlignRight
-                        text: graphPointData.sha1
-                        color: "e95f8cf36d49d7f5c084d8c17bc791170739917e" === graphPointData.sha1 ?
-                                   "red" : "black"
-                    }
-                    Row {
-                        anchors.left: parent.left
-                        Text {
-                            verticalAlignment: Text.AlignVCenter
-                            horizontalAlignment: Text.AlignLeft
-                            text: graphPointData.branch
-                        }
-                        Text {
-                            verticalAlignment: Text.AlignVCenter
-                            horizontalAlignment: Text.AlignLeft
-                            text: graphPointData.tag
-                        }
-                    }
 
-                    MouseArea {
-                        id: textSelector
-                        anchors.fill: parent
-                        hoverEnabled: true
-                        onClicked: {
-                            commitBody.visible = true
-                            commitBodyText.text = _handler.graph.commitData(graphPointData)
-                        }
-                    }
+                console.log("Length" + root.commitsForDiff.length)
+                root.commitsForDiff.push(commit)
+                if(root.commitsForDiff.length === 2) {
+                    commitPlane.commit = root.commitsForDiff[1]
+                    commitPlane.diff = _handler.diff(root.commitsForDiff[0], root.commitsForDiff[1])
+                    root.commitsForDiff=[]
                 }
             }
         }
     }
 
-    FileDialog {
-        id: repoOpenDialog
-        folder: "."
-        selectFolder: true
-        selectMultiple: false
-        onAccepted: {
-            _handler.open(repoOpenDialog.fileUrl)
-        }
+    CommitPlane {
+        id: commitPlane
+        anchors.left: commitList.right
+        anchors.right: parent.right
+        anchors.top: topBar.bottom
+        anchors.bottom: parent.bottom
+//        visible: diff != null
+//        opacity: diff != null ? 1.0 : 0.0
     }
 
-    Rectangle {
-        id: commitBody
-        anchors.left: parent.left
-        anchors.top: parent.top
-        anchors.bottom: parent.bottom
-        width: parent.width/2
-        color: "white"
-        visible: false
-        MouseArea {
-            anchors.fill: parent
-            onClicked: {
-                commitBody.visible = false;
-            }
+    Keys.onPressed: {
+        if(event.key === Qt.Key_Control) {
+            root.controlActive = true
+            console.log("control pressed");
         }
-        Text {
-            id: commitBodyText
-            anchors.fill: parent
+    }
+
+    Keys.onReleased: {
+        if(event.key === Qt.Key_Control) {
+            root.controlActive = false
+            console.log("control released");
         }
     }
 }

+ 55 - 0
qml/TopBar.qml

@@ -0,0 +1,55 @@
+import QtQuick 2.0
+import QtQuick.Dialogs 1.2
+import QtQuick.Controls 1.4
+Item {
+    id: root
+    anchors.right: parent.right
+    anchors.left: parent.left
+    height: childrenRect.height
+    signal closeClicked()
+    property alias closeVisible: closeButton.visible
+    Row {
+        spacing: 10
+        height: 50
+        Text {
+            text: "Active repository" + repoOpenDialog.fileUrl
+        }
+        Button {
+            text: "Choose..."
+            onClicked: repoOpenDialog.open()
+        }
+
+        Text {
+            text: _handler.activeRepo.name
+        }
+
+        FileDialog {
+            id: repoOpenDialog
+            folder: "."
+            selectFolder: true
+            selectMultiple: false
+            onAccepted: {
+    //TODO: repo open is not available
+    //            _handler.open(repoOpenDialog.fileUrl)
+            }
+        }
+
+    }
+
+    Image {
+        id: closeButton
+        anchors.right: parent.right
+        anchors.top: parent.top
+        anchors.topMargin: 10
+        anchors.rightMargin: 10
+        source: closeControl.pressed ? "qrc:///images/x-mark-3-24_active.png" : "qrc:///images/x-mark-3-24.png"
+        MouseArea {
+            id: closeControl
+            anchors.fill: parent
+            onClicked: {
+                root.closeClicked()
+            }
+        }
+    }
+}
+

+ 25 - 0
resources.qrc

@@ -1,5 +1,30 @@
 <RCC>
     <qresource prefix="/">
         <file>qml/MainView.qml</file>
+        <file>fonts/Inconsolata.otf</file>
+        <file>qml/Graph.qml</file>
+        <file>qml/CommitList.qml</file>
+        <file>qml/DiffFiles.qml</file>
+        <file>images/x-mark-3-48.png</file>
+        <file>images/x-mark-3-48_active.png</file>
+        <file>images/x-mark-3-24_active.png</file>
+        <file>images/x-mark-3-24.png</file>
+        <file>qml/TopBar.qml</file>
+        <file>qml/FlickPager.qml</file>
+        <file>images/arrow-141-24_active.png</file>
+        <file>images/arrow-141-24.png</file>
+        <file>images/arrow-204-24_active.png</file>
+        <file>images/arrow-204-24.png</file>
+        <file>qml/FlickPagerArrow.qml</file>
+        <file>images/arrow-141-16_active.png</file>
+        <file>images/arrow-141-16.png</file>
+        <file>images/arrow-204-16_active.png</file>
+        <file>images/arrow-204-16.png</file>
+        <file>qml/AreaPlaceholder.qml</file>
+        <file>qml/CommitPlane.qml</file>
+        <file>qml/CommitInfo.qml</file>
+        <file>qml/CommitInfoLine.qml</file>
+        <file>images/arrow-203-16_active.png</file>
+        <file>images/arrow-203-16.png</file>
     </qresource>
 </RCC>

+ 9 - 2
universallistmodel.h

@@ -29,9 +29,10 @@ public:
     QHash<int, QByteArray> roleNames() const {
         if(s_roleNames.isEmpty()) {
             int propertyCount = T::staticMetaObject.propertyCount();
-            for(int i = 0; i < propertyCount; i++) {
+            for(int i = 1; i < propertyCount; i++) {
                 s_roleNames.insert(Qt::UserRole + i, T::staticMetaObject.property(i).name());
             }
+            s_roleNames[propertyCount] = "modelData";
         }
         return s_roleNames;
     }
@@ -45,6 +46,10 @@ public:
         }
 
         T* dataPtr = m_container.at(row).data();
+
+        if(s_roleNames.value(role) == "modelData") {
+            return QVariant::fromValue(dataPtr);
+        }
         return dataPtr->property(s_roleNames.value(role));
     }
 
@@ -85,7 +90,9 @@ public:
 protected:
     void clear() {
         foreach (const QPointer<T>& value, m_container) {
-            delete value.data();
+            if(!value.isNull()) {
+                delete value.data();
+            }
         }
         m_container.clear();
     }