changeset 677:0329bbd4b57c

Merge from branch "help"
author Chris Cannam
date Thu, 06 Dec 2018 13:54:34 +0000
parents cb4f7c3c01dd (diff) 1fc2e0269ae0 (current diff)
children c0b46d0514a7 ad3e5693cb76 21e03596dd93
files src/mainwindow.cpp src/multichoicedialog.cpp src/multichoicedialog.h
diffstat 239 files changed, 18973 insertions(+), 14980 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.appveyor.yml	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,15 @@
+
+image:
+  - Visual Studio 2017
+
+configuration:
+  - Release
+
+install:
+  - ps: '"[hostfingerprints]" | Out-File -Encoding "ASCII" -Append $env:USERPROFILE\mercurial.ini'
+  - ps: '"code.soundsoftware.ac.uk = 66:ef:e2:0e:e3:55:93:9a:33:aa:2a:e9:fe:be:21:c2:a2:8d:4f:f1" | Out-File -Encoding "ASCII" -Append $env:USERPROFILE\mercurial.ini'
+  - ps: '"[hostsecurity]" | Out-File -Encoding "ASCII" -Append $env:USERPROFILE\mercurial.ini'
+  - ps: '"code.soundsoftware.ac.uk = code.soundsoftware.ac.uk:fingerprints=sha256:64:75:f6:47:15:de:b4:51:ea:96:e2:f4:8a:f5:53:a5:11:c8:dd:82:73:5d:bd:54:18:cb:c8:9d:10:37:28:85" | Out-File -Encoding "ASCII" -Append $env:USERPROFILE\mercurial.ini'
+
+build_script:
+  - deploy\win32\build.bat
--- a/.hgignore	Tue Mar 15 12:36:26 2011 +0000
+++ b/.hgignore	Thu Dec 06 13:54:34 2018 +0000
@@ -1,22 +1,29 @@
-syntax: glob
-*.core
-*.o
-*~
-*.exe
-*.dll
-*.pyc
-*.orig
-*.user
-moc_*
-qrc_*
-o/*
-core
-easyhg
-debug/*
-release/*
-Makefile
-Makefile.Debug
-Makefile.Release
-*.app/*
-.DS_Store
-*.pdb
+syntax: glob
+*.core
+*.o
+*~
+*.exe
+*.dll
+*.pyc
+*.orig
+*.user
+moc_*
+qrc_*
+o/*
+core
+easyhg
+debug/*
+release/*
+Makefile
+Makefile.Debug
+Makefile.Release
+*.app/*
+.DS_Store
+*.pdb
+re:^EasyMercurial$
+re:^kdiff3$
+re:^_UpgradeReport_Files/
+*.dmg
+*.xcodeproj
+*.bak
+*.msi
--- a/.hgtags	Tue Mar 15 12:36:26 2011 +0000
+++ b/.hgtags	Thu Dec 06 13:54:34 2018 +0000
@@ -9,3 +9,16 @@
 4b9656471303408a2bd2b40656cd52a44305211d easyhg_v0.4
 124a3ea9fafb556cc47ec8f3d4cfdad3fd8596aa easyhg_v0.4
 a5813b625c6e8ea5f33b4c002bd8d91204f74c4b easyhg_v0.9
+820512d49fcac53b902a053cede055b903400006 easyhg_v0.9.5
+a206deb6c1aab16f5bfb4c1d9d10074d1a93fa7e easyhg_v0.9.6
+9510a32a96ab9ea3c2bec5998f3f702b29ec0114 easyhg_v0.9.7
+319f920a51ee61df29701db8ad9bdb413c66a399 easyhg_v0.9.8
+6bb2a1f3087cd57d5a3296bbb82a494b7fa609c6 easyhg_v1.0
+06507a59f2b8f5db9ae37e0dcd37a320e03ae8b8 easyhg_v1.1
+ca943d5147697d8a63d835e1227315fefee0691b easyhg_v1.2
+ca943d5147697d8a63d835e1227315fefee0691b easyhg_v1.2
+9b300409c184d47d57fba49341b2a1818b7fd0bc easyhg_v1.2
+8bcf7ce9b1d209b94f71593545fc5de7c008f5a3 easyhg_v1.2_win32_2
+abfef4acceca78078fe0c3b7fa8794f9ec77a4d3 easyhg_v1.2.1
+aade37785eca08d98afa7d12a66a181e1a2f4e2c easyhg_v1.2.2
+4d39a93cab9e2ba6ca08b17781b2a37ab4938e89 easyhg_v1.3.0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.travis.yml	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,24 @@
+language:
+  - cpp
+
+matrix:
+  include:
+    - os: osx
+      osx_image: xcode9
+    - os: linux
+      dist: xenial
+      addons:
+        apt:
+          packages:
+           - qt5-default libqt5svg5-dev autoconf libtool git mercurial
+
+before_install:
+  - if [[ "$TRAVIS_OS_NAME" = "osx" ]] ; then brew update ; fi
+  - if [[ "$TRAVIS_OS_NAME" = "osx" ]] ; then brew install qt5 ; fi
+  - if [[ "$TRAVIS_OS_NAME" = "osx" ]] ; then export PATH=$PATH:/usr/local/opt/qt5/bin ; fi
+
+before_script:
+  - qmake -r easyhg.pro
+
+script:
+  - make -j3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CHANGELOG	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,51 @@
+
+Changes in v1.3.0 since v1.2.2:
+ * Some new preferences and adjustments to prefs layout (Sam Izzo)
+ * Some new keyboard shortcuts (Sam Izzo)
+ * Add "Show in Explorer" to context menu (platform-dependent) (Sam Izzo)
+ * Avoid reseeking history to bottom on closing a branch (Felipe Pozo)
+ * Add optional limit to number of items in graph (Mikel Fernandez)
+ * Use HKMU registry option for smoother install on Windows multi-user setup
+ * Show tag if present on merge commit
+ * Avoid issuing dire warning when closing only one head of default branch
+ * Fix toolbar layout when building with Qt 4.8
+
+Changes in v1.2.2 since v1.2.1:
+ * Fix failure to provide IV arg to AES CBC constructor in PyCrypto
+ * Update the history after a failed merge to show the merge
+ * Clear the history while cloning a new repo
+ * Show a sensible error when clone target can't be created
+ * Minor improvements to zooming in history widget
+ * Various other minor fixes
+
+Changes in v1.2.1 since v1.2:
+ * Fix a filesystem watcher bug (affecting OS/X only)
+
+Changes in v1.2 since v1.1:
+
+ * Add a Find function to both My Work and History tabs
+ * Add a Cancel button to the progress bar in the status line when
+   carrying out network operations
+ * Remove Refresh button from the toolbar. This button was found to be
+   causing significant confusion, as some users interpreted it as
+   meaning resynchronise with the server, while in fact all it did was
+   re-read the local working copy. The function is still present, but
+   has been relegated to File -> Re-Read Working Folder
+ * Rework the filesystem watcher code, making it generally more
+   reliable (a requirement for removing the Refresh button)
+ * Fix problem with return codes in Mercurial 2.1 causing push and
+   pull to be reported as failing
+ * Make it possible to clear the remote repository URL
+ * Switch the Windows installer from InstallJammer to WiX
+
+Changes in v1.1 since v1.0.1:
+
+ * Closed branches are now supported: you can close a branch in EasyMercurial
+   and closed branches are truncated by default
+ * Bookmarks are now displayed, though you still cannot create them
+ * Fix excessive window size when very long URL or filename used
+ * Fix inconsistent state following a merge that results in no changes
+ * Improve server port detection when sharing repository
+ * Add help topic for merge
+ * Update program icon!
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,52 @@
+
+EasyMercurial
+=============
+
+EasyMercurial is a user interface for the Mercurial distributed
+version control system.
+
+EasyMercurial is intended to be:
+
+ * simple to teach and to learn
+ * indicative of repository state using a history graph representation
+ * recognisably close to normal command-line workflow for Mercurial
+ * consistent across platforms
+
+We are not trying to produce "the best" Mercurial client for any one
+purpose. We actively encourage users to move on to other clients as
+their needs evolve. The aim is simply to provide something accessible
+for beginners in small project groups working with a shared remote
+repository.
+
+The application is developed by Chris Cannam for SoundSoftware.ac.uk,
+based on the HgExplorer application by Jari Korhonen, and is published
+under the GPL.  See the file COPYING for license details.
+
+
+Building EasyMercurial
+======================
+
+EasyMercurial is written in C++ using the Qt4 toolkit.  On most
+platforms, you can build it by running "qmake" followed by "make".  Qt
+version 4.6 or newer is required.
+
+
+To run EasyMercurial
+====================
+
+Just run the EasyMercurial application that is produced by the build.
+You will of course also need to have Mercurial installed (version 1.7
+or newer).  If you want to use the EasyHg authentication extension,
+you will also need PyQt4 (the Python bindings for Qt4); you may also
+wish to install the python-crypto library for the password store.
+Finally, an external diff/merge utility is required, typically kdiff3.
+
+
+EasyMercurial is
+Copyright 2010 Jari Korhonen
+Copyright 2010-2013 Chris Cannam
+Copyright 2010-2013 Queen Mary, University of London
+
+
+
+
--- a/annotatedialog.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 "annotatedialog.h"
-#include "common.h"
-#include "colourset.h"
-#include "debug.h"
-
-#include <QDialogButtonBox>
-#include <QLabel>
-#include <QTableWidget>
-#include <QHeaderView>
-#include <QGridLayout>
-
-AnnotateDialog::AnnotateDialog(QWidget *w, QString text) :
-    QDialog(w)
-{
-    setMinimumWidth(800);
-    setMinimumHeight(500);
-
-    text.replace("\r\n", "\n");
-    QStringList lines = text.split("\n");
-
-    QGridLayout *layout = new QGridLayout;
-    QTableWidget *table = new QTableWidget;
-
-    QRegExp annotateLineRE = QRegExp("^([^:]+) ([a-z0-9]{12}) ([0-9-]+): (.*)$");
-
-    table->setRowCount(lines.size());
-    table->setColumnCount(4);
-    table->horizontalHeader()->setStretchLastSection(true);
-    table->verticalHeader()->setDefaultSectionSize
-	(table->verticalHeader()->fontMetrics().height() + 2);
-
-    QStringList labels;
-    labels << tr("User") << tr("Revision") << tr("Date") << tr("Content");
-    table->setHorizontalHeaderLabels(labels);
-
-    table->setShowGrid(false);
-
-    QFont monofont("Monospace");
-    monofont.setStyleHint(QFont::TypeWriter);
-
-    int row = 0;
-
-    foreach (QString line, lines) {
-	if (annotateLineRE.indexIn(line) == 0) {
-	    QStringList items = annotateLineRE.capturedTexts();
-	    QString id = items[2];
-	    QColor colour = ColourSet::instance()->getColourFor(id);
-	    QColor bg = QColor::fromHsv(colour.hue(),
-					30,
-					230);
-	    // note items[0] is the whole match, so we want 1-4
-	    for (int col = 0; col+1 < items.size(); ++col) {
-		QString item = items[col+1];
-		if (col == 0) item = item.trimmed();
-		QTableWidgetItem *wi = new QTableWidgetItem(item);
-		wi->setFlags(Qt::ItemIsEnabled);
-		wi->setBackground(bg);
-		if (col == 3) { // id, content
-		    wi->setFont(monofont);
-		}
-		table->setItem(row, col, wi);
-	    }
-	} else {
-	    DEBUG << "AnnotateDialog: Failed to match RE in line: " << line << " at row " << row << endl;
-	}
-	++row;
-    }
-
-    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
-    connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
-
-    layout->addWidget(table, 0, 0);
-    layout->addWidget(bb, 1, 0);
-
-    setLayout(layout);
-}
-
--- a/annotatedialog.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 ANNOTATE_DIALOG_H
-#define ANNOTATE_DIALOG_H
-
-#include <QDialog>
-
-class AnnotateDialog : public QDialog
-{
-    Q_OBJECT
-
-public:
-    AnnotateDialog(QWidget *parent, QString output);
-};
-
-#endif
--- a/changeset.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "changeset.h"
-#include "common.h"
-
-#include <QVariant>
-
-Changeset::Changeset(const LogEntry &e)
-{
-    foreach (QString key, e.keys()) {
-        if (key == "parents") {
-            QStringList parents = e.value(key).split
-                (" ", QString::SkipEmptyParts);
-            setParents(parents);
-        } else if (key == "tag") {
-            QStringList tags = e.value(key).split
-                (" ", QString::SkipEmptyParts);
-            setTags(tags);
-        } else if (key == "timestamp") {
-            setTimestamp(e.value(key).split(" ")[0].toULongLong());
-        } else if (key == "changeset") {
-            setId(e.value(key));
-        } else {
-            setProperty(key.toLocal8Bit().data(), e.value(key));
-        }
-    }
-}
-
-QString Changeset::getLogTemplate()
-{
-    return "id: {rev}:{node|short}\\nauthor: {author}\\nbranch: {branches}\\ntag: {tags}\\ndatetime: {date|isodate}\\ntimestamp: {date|hgdate}\\nage: {date|age}\\nparents: {parents}\\ncomment: {desc|json}\\n\\n";
-}
-
-QString Changeset::formatHtml()
-{
-    QString description;
-    QString rowTemplate = "<tr><td><b>%1</b>&nbsp;</td><td>%2</td></tr>";
-
-    description = "<qt><table border=0>";
-
-    QString c = comment().trimmed();
-    c = c.replace(QRegExp("^\""), "");
-    c = c.replace(QRegExp("\"$"), "");
-    c = c.replace("\\\"", "\"");
-    c = xmlEncode(c);
-    c = c.replace("\\n", "<br>");
-
-    QStringList propNames, propTexts;
-    
-    propNames << "id"
-	      << "author"
-	      << "datetime"
-	      << "branch"
-	      << "tags"
-	      << "comment";
-
-    propTexts << QObject::tr("Identifier:")
-	      << QObject::tr("Author:")
-	      << QObject::tr("Date:")
-	      << QObject::tr("Branch:")
-	      << QObject::tr("Tag:")
-	      << QObject::tr("Comment:");
-
-    for (int i = 0; i < propNames.size(); ++i) {
-	QString prop = propNames[i];
-	QString value;
-        if (prop == "id") {
-            value = hashOf(id());
-        } else if (prop == "comment") {
-            value = c;
-        } else if (prop == "tags") {
-            value = tags().join(" ");
-        } else {
-	    value = xmlEncode(property(prop.toLocal8Bit().data()).toString());
-	}
-	if (value != "") {
-	    description += rowTemplate
-		.arg(xmlEncode(propTexts[i]))
-		.arg(value);
-	}
-    }
-
-    description += "</table></qt>";
-
-    return description;
-}
--- a/changeset.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 CHANGESET_H
-#define CHANGESET_H
-
-#include <QObject>
-#include <QString>
-#include <QStringList>
-#include <QList>
-#include <QSharedPointer>
-
-#include "logparser.h"
-
-class Changeset;
-
-typedef QList<Changeset *> Changesets;
-
-class Changeset : public QObject
-{
-    Q_OBJECT
-
-    Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged STORED true);
-    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged STORED true);
-    Q_PROPERTY(QString branch READ branch WRITE setBranch NOTIFY branchChanged STORED true);
-    Q_PROPERTY(QStringList tags READ tags WRITE setTags NOTIFY tagsChanged STORED true);
-    Q_PROPERTY(QString datetime READ datetime WRITE setDatetime NOTIFY datetimeChanged STORED true);
-    Q_PROPERTY(qulonglong timestamp READ timestamp WRITE setTimestamp NOTIFY timestampChanged STORED true);
-    Q_PROPERTY(QString age READ age WRITE setAge NOTIFY ageChanged STORED true);
-    Q_PROPERTY(QStringList parents READ parents WRITE setParents NOTIFY parentsChanged STORED true);
-    Q_PROPERTY(QStringList children READ children WRITE setChildren NOTIFY childrenChanged STORED true);
-    Q_PROPERTY(QString comment READ comment WRITE setComment NOTIFY commentChanged STORED true);
-
-public:
-    Changeset() : QObject() { }
-    explicit Changeset(const LogEntry &e);
-
-    QString id() const { return m_id; }
-    QString author() const { return m_author; }
-    QString branch() const { return m_branch; }
-    QStringList tags() const { return m_tags; }
-    QString datetime() const { return m_datetime; }
-    qulonglong timestamp() const { return m_timestamp; }
-    QString age() const { return m_age; }
-    QStringList parents() const { return m_parents; }
-    QString comment() const { return m_comment; }
-
-    /**
-     * The children property is not obtained from Hg, but set in
-     * Grapher::layout() based on reported parents
-     */
-    QStringList children() const { return m_children; }
-
-    int number() const {
-        return id().split(':')[0].toInt();
-    }
-
-    QString authorName() const {
-	QString a = author();
-	return a.replace(QRegExp("\\s*<[^>]*>"), "");
-    }
-
-    QString date() const {
-        return datetime().split(' ')[0];
-    }
-
-    bool isOnBranch(QString branch) {
-        QString b = m_branch;
-        if (branch == "") branch = "default";
-        if (b == "") b = "default";
-        if (branch == b) return true;
-        return false;
-    }
-
-    static QString hashOf(QString id) {
-        return id.split(':')[1];
-    }
-
-    static QStringList getIds(Changesets csets) {
-        QStringList ids;
-        foreach (Changeset *cs, csets) ids.push_back(cs->id());
-        return ids;
-    }
-
-    static Changesets parseChangesets(QString logText) {
-        Changesets csets;
-        LogList log = LogParser(logText).parse();
-        foreach (LogEntry e, log) {
-            csets.push_back(new Changeset(e));
-        }
-        return csets;
-    }
-
-    static QString getLogTemplate();
-
-    QString formatHtml();
-    
-signals:
-    void idChanged(QString id);
-    void authorChanged(QString author);
-    void branchChanged(QString branch);
-    void tagsChanged(QStringList tags);
-    void datetimeChanged(QString datetime);
-    void timestampChanged(qulonglong timestamp);
-    void ageChanged(QString age);
-    void parentsChanged(QStringList parents);
-    void childrenChanged(QStringList children);
-    void commentChanged(QString comment);
-
-public slots:
-    void setId(QString id) { m_id = id; emit idChanged(id); }
-    void setAuthor(QString author) { m_author = author; emit authorChanged(author); }
-    void setBranch(QString branch) { m_branch = branch; emit branchChanged(branch); }
-    void setTags(QStringList tags) { m_tags = tags; emit tagsChanged(tags); }
-    void addTag(QString tag) { m_tags.push_back(tag); emit tagsChanged(m_tags); }
-    void setDatetime(QString datetime) { m_datetime = datetime; emit datetimeChanged(datetime); }
-    void setTimestamp(qulonglong timestamp) { m_timestamp = timestamp; emit timestampChanged(timestamp); }
-    void setAge(QString age) { m_age = age; emit ageChanged(age); }
-    void setParents(QStringList parents) { m_parents = parents; emit parentsChanged(parents); }
-    void setChildren(QStringList children) { m_children = children; emit childrenChanged(m_children); }
-    void addChild(QString child) { m_children.push_back(child); emit childrenChanged(m_children); }
-    void setComment(QString comment) { m_comment = comment; emit commentChanged(comment); }
-
-private:
-    QString m_id;
-    QString m_author;
-    QString m_branch;
-    QStringList m_tags;
-    QString m_datetime;
-    qulonglong m_timestamp;
-    QString m_age;
-    QStringList m_parents;
-    QStringList m_children;
-    QString m_comment;
-};
-
-
-#endif // CHANGESET_H
--- a/changesetdetailitem.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "changesetdetailitem.h"
-#include "changeset.h"
-#include "textabbrev.h"
-#include "colourset.h"
-#include "debug.h"
-#include "common.h"
-
-#include <QTextDocument>
-#include <QPainter>
-
-ChangesetDetailItem::ChangesetDetailItem(Changeset *cs) :
-    m_changeset(cs), m_doc(0)
-{
-    m_font = QFont();
-    m_font.setPixelSize(11);
-    m_font.setBold(false);
-    m_font.setItalic(false);
-
-    makeDocument();
-}
-
-ChangesetDetailItem::~ChangesetDetailItem()
-{
-    delete m_doc;
-}
-
-QRectF
-ChangesetDetailItem::boundingRect() const
-{
-    int w = 350;
-    m_doc->setTextWidth(w);
-    return QRectF(-10, -10, w + 10, m_doc->size().height() + 10);
-}
-
-void
-ChangesetDetailItem::paint(QPainter *paint,
-			   const QStyleOptionGraphicsItem *option,
-			   QWidget *w)
-{
-    paint->save();
-    
-    ColourSet *colourSet = ColourSet::instance();
-    QColor branchColour = colourSet->getColourFor(m_changeset->branch());
-    QColor userColour = colourSet->getColourFor(m_changeset->author());
-
-    QFont f(m_font);
-
-    QTransform t = paint->worldTransform();
-    float scale = std::min(t.m11(), t.m22());
-    if (scale > 1.0) {
-	int ps = int((f.pixelSize() / scale) + 0.5);
-	if (ps < 8) ps = 8;
-	f.setPixelSize(ps);
-    }
-
-    if (scale < 0.1) {
-	paint->setPen(QPen(branchColour, 0));
-    } else {
-	paint->setPen(QPen(branchColour, 2));
-    }
-
-    paint->setFont(f);
-    QFontMetrics fm(f);
-    int fh = fm.height();
-
-    int width = 350;
-    m_doc->setTextWidth(width);
-    int height = m_doc->size().height();
-
-    QRectF r(0.5, 0.5, width - 1, height - 1);
-    paint->setBrush(Qt::white);
-    paint->drawRect(r);
-
-    if (scale < 0.1) {
-	paint->restore();
-	return;
-    }
-
-    // little triangle connecting to its "owning" changeset item
-    paint->setBrush(branchColour);
-    QVector<QPointF> pts;
-    pts.push_back(QPointF(0, height/3 - 5));
-    pts.push_back(QPointF(0, height/3 + 5));
-    pts.push_back(QPointF(-10, height/3));
-    pts.push_back(QPointF(0, height/3 - 5));
-    paint->drawPolygon(QPolygonF(pts));
-
-/*
-    paint->setBrush(branchColour);
-    QVector<QPointF> pts;
-    pts.push_back(QPointF(width/2 - 5, 0));
-    pts.push_back(QPointF(width/2 + 5, 0));
-    pts.push_back(QPointF(width/2, -10));
-    pts.push_back(QPointF(width/2 - 5, 0));
-    paint->drawPolygon(QPolygonF(pts));
-*/
-    m_doc->drawContents(paint, r);
-
-    paint->restore();
-}
-
-void
-ChangesetDetailItem::makeDocument()
-{
-    delete m_doc;
-    m_doc = new QTextDocument;
-    m_doc->setHtml(m_changeset->formatHtml());
-}
-
--- a/changesetdetailitem.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 CHANGESETDETAILITEM_H
-#define CHANGESETDETAILITEM_H
-
-#include <QGraphicsItem>
-#include <QFont>
-
-class Changeset;
-
-class ChangesetDetailItem : public QGraphicsItem
-{
-public:
-    ChangesetDetailItem(Changeset *cs);
-    virtual ~ChangesetDetailItem();
-
-    virtual QRectF boundingRect() const;
-    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
-
-    Changeset *getChangeset() { return m_changeset; }
-
-private:
-    QFont m_font;
-    Changeset *m_changeset;
-    QTextDocument *m_doc;
-
-    void makeDocument();
-};
-
-#endif // CHANGESETDETAILITEM_H
--- a/changesetitem.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,383 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "changesetitem.h"
-#include "changesetscene.h"
-#include "changesetdetailitem.h"
-#include "changeset.h"
-#include "textabbrev.h"
-#include "colourset.h"
-#include "debug.h"
-
-#include <QPainter>
-#include <QGraphicsScene>
-#include <QGraphicsSceneMouseEvent>
-#include <QMenu>
-#include <QAction>
-#include <QLabel>
-#include <QWidgetAction>
-#include <QApplication>
-#include <QClipboard>
-
-ChangesetItem::ChangesetItem(Changeset *cs) :
-    m_changeset(cs), m_detail(0),
-    m_showBranch(false), m_column(0), m_row(0), m_wide(false),
-    m_current(false), m_new(false)
-{
-    m_font = QFont();
-    m_font.setPixelSize(11);
-    m_font.setBold(false);
-    m_font.setItalic(false);
-    setCursor(Qt::ArrowCursor);
-}
-
-QString
-ChangesetItem::getId()
-{
-    return m_changeset->id();
-}
-
-QRectF
-ChangesetItem::boundingRect() const
-{
-    int w = 100;
-    if (m_wide) w = 180;
-    return QRectF(-((w-50)/2 - 1), -30, w - 3, 90);
-}
-
-void
-ChangesetItem::showDetail()
-{
-    if (m_detail) return;
-    m_detail = new ChangesetDetailItem(m_changeset);
-    m_detail->setZValue(zValue() + 1);
-    scene()->addItem(m_detail);
-    int w = 100;
-    if (m_wide) w = 180;
-    int h = 80;
-//    m_detail->moveBy(x() - (m_detail->boundingRect().width() - 50) / 2,
-//                     y() + 60);
-    m_detail->moveBy(x() + (w + 50) / 2 + 10 + 0.5,
-                     y() - (m_detail->boundingRect().height() - h) / 2 + 0.5);
-    emit detailShown();
-}    
-
-void
-ChangesetItem::hideDetail()
-{
-    if (!m_detail) return;
-    scene()->removeItem(m_detail);
-    delete m_detail;
-    m_detail = 0;
-    emit detailHidden();
-}    
-
-void
-ChangesetItem::mousePressEvent(QGraphicsSceneMouseEvent *e)
-{
-    DEBUG << "ChangesetItem::mousePressEvent" << endl;
-    if (e->button() == Qt::LeftButton) {
-        if (m_detail) {
-            hideDetail();
-        } else {
-            showDetail();
-        }
-    } else if (e->button() == Qt::RightButton) {
-        if (m_detail) {
-            hideDetail();
-        }
-        activateMenu();
-    }
-}
-
-void
-ChangesetItem::activateMenu()
-{
-    m_parentDiffActions.clear();
-    m_summaryActions.clear();
-
-    QMenu *menu = new QMenu;
-    QLabel *label = new QLabel(tr("<qt><b>&nbsp;Revision: </b>%1</qt>")
-                               .arg(Changeset::hashOf(m_changeset->id())));
-    QWidgetAction *wa = new QWidgetAction(menu);
-    wa->setDefaultWidget(label);
-    menu->addAction(wa);
-    menu->addSeparator();
-
-    QAction *copyId = menu->addAction(tr("Copy identifier to clipboard"));
-    connect(copyId, SIGNAL(triggered()), this, SLOT(copyIdActivated()));
-
-    QAction *stat = menu->addAction(tr("Summarise changes"));
-    connect(stat, SIGNAL(triggered()), this, SLOT(showSummaryActivated()));
-
-    menu->addSeparator();
-
-    QStringList parents = m_changeset->parents();
-
-    QString leftId, rightId;
-    bool havePositions = false;
-
-    if (parents.size() > 1) {
-        ChangesetScene *cs = dynamic_cast<ChangesetScene *>(scene());
-        if (cs && parents.size() == 2) {
-            ChangesetItem *i0 = cs->getItemById(parents[0]);
-            ChangesetItem *i1 = cs->getItemById(parents[1]);
-            if (i0 && i1) {
-                if (i0->x() < i1->x()) {
-                    leftId = parents[0];
-                    rightId = parents[1];
-                } else {
-                    leftId = parents[1];
-                    rightId = parents[0];
-                }
-                havePositions = true;
-            }
-        }
-    }
-
-    if (parents.size() > 1) {
-        if (havePositions) {
-            
-            QAction *diff = menu->addAction(tr("Diff to left parent"));
-            connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated()));
-            m_parentDiffActions[diff] = leftId;
-            
-            diff = menu->addAction(tr("Diff to right parent"));
-            connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated()));
-            m_parentDiffActions[diff] = rightId;
-
-        } else {
-
-            foreach (QString parentId, parents) {
-                QString text = tr("Diff to parent %1").arg(Changeset::hashOf(parentId));
-                QAction *diff = menu->addAction(text);
-                connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated()));
-                m_parentDiffActions[diff] = parentId;
-            }
-        }
-
-    } else {
-
-        QAction *diff = menu->addAction(tr("Diff to parent"));
-        connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated()));
-    }
-
-    QAction *diffCurrent = menu->addAction(tr("Diff to current working folder"));
-    connect(diffCurrent, SIGNAL(triggered()), this, SLOT(diffToCurrentActivated()));
-
-    menu->addSeparator();
-
-    QAction *update = menu->addAction(tr("Update to this revision"));
-    connect(update, SIGNAL(triggered()), this, SLOT(updateActivated()));
-
-    QAction *merge = menu->addAction(tr("Merge from here to current"));
-    connect(merge, SIGNAL(triggered()), this, SLOT(mergeActivated()));
-
-    menu->addSeparator();
-
-    QAction *branch = menu->addAction(tr("Start new branch..."));
-    branch->setEnabled(m_current);
-    connect(branch, SIGNAL(triggered()), this, SLOT(newBranchActivated()));
-
-    QAction *tag = menu->addAction(tr("Add tag..."));
-    connect(tag, SIGNAL(triggered()), this, SLOT(tagActivated()));
-
-    menu->exec(QCursor::pos());
-
-    ungrabMouse();
-}
-
-void
-ChangesetItem::copyIdActivated()
-{
-    QClipboard *clipboard = QApplication::clipboard();
-    clipboard->setText(Changeset::hashOf(m_changeset->id()));
-}
-
-void ChangesetItem::diffToParentActivated()
-{
-    QAction *a = qobject_cast<QAction *>(sender());
-    QString parentId;
-    if (m_parentDiffActions.contains(a)) {
-        parentId = m_parentDiffActions[a];
-        DEBUG << "ChangesetItem::diffToParentActivated: specific parent " 
-              << parentId << " selected" << endl;
-    } else {
-        parentId = m_changeset->parents()[0];
-        DEBUG << "ChangesetItem::diffToParentActivated: "
-              << "no specific parent selected, using first parent "
-              << parentId << endl;
-    }
-
-    emit diffToParent(getId(), parentId);
-}
-
-void ChangesetItem::showSummaryActivated()
-{
-    emit showSummary(m_changeset);
-}
-
-void ChangesetItem::updateActivated() { emit updateTo(getId()); }
-void ChangesetItem::diffToCurrentActivated() { emit diffToCurrent(getId()); }
-void ChangesetItem::mergeActivated() { emit mergeFrom(getId()); }
-void ChangesetItem::tagActivated() { emit tag(getId()); }
-void ChangesetItem::newBranchActivated() { emit newBranch(getId()); }
-
-void
-ChangesetItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
-{
-    paint->save();
-    
-    ColourSet *colourSet = ColourSet::instance();
-    QColor branchColour = colourSet->getColourFor(m_changeset->branch());
-    QColor userColour = colourSet->getColourFor(m_changeset->author());
-
-    QFont f(m_font);
-
-    QTransform t = paint->worldTransform();
-    float scale = std::min(t.m11(), t.m22());
-    if (scale > 1.0) {
-	int ps = int((f.pixelSize() / scale) + 0.5);
-	if (ps < 8) ps = 8;
-	f.setPixelSize(ps);
-    }
-
-    bool showText = (scale >= 0.2);
-    bool showProperLines = (scale >= 0.1);
-
-    if (!showProperLines) {
-	paint->setPen(QPen(branchColour, 0));
-    } else {
-	paint->setPen(QPen(branchColour, 2));
-    }
-	
-    paint->setFont(f);
-    QFontMetrics fm(f);
-    int fh = fm.height();
-
-    int width = 100;
-    if (m_wide) width = 180;
-    int x0 = -((width - 50) / 2 - 1);
-
-    int textwid = width - 7;
-
-    QString comment;
-    QStringList lines;
-    int lineCount = 3;
-
-    if (showText) {
-    
-        comment = m_changeset->comment().trimmed();
-        comment = comment.replace("\\n", " ");
-        comment = comment.replace(QRegExp("^\"\\s*\\**\\s*"), "");
-        comment = comment.replace(QRegExp("\"$"), "");
-        comment = comment.replace("\\\"", "\"");
-
-        comment = TextAbbrev::abbreviate(comment, fm, textwid,
-                                         TextAbbrev::ElideEnd, "...", 3);
-        // abbreviate() changes this (ouch!), restore it
-        textwid = width - 5;
-
-        lines = comment.split('\n');
-        lineCount = lines.size();
-
-        if (lineCount < 2) lineCount = 2;
-    }
-
-    int height = (lineCount + 1) * fh + 2;
-    QRectF r(x0, 0, width - 3, height);
-
-    if (showProperLines) {
-
-        paint->setBrush(Qt::white);
-
-        if (m_current) {
-            paint->drawRect(QRectF(x0 - 4, -4, width + 5, height + 8));
-        }
-
-        if (m_new) {
-            paint->save();
-            paint->setPen(Qt::yellow);
-            paint->setBrush(Qt::NoBrush);
-            paint->drawRect(QRectF(x0 - 2, -2, width + 1, height + 4));
-            paint->restore();
-        }
-    }
-
-    paint->drawRect(r);
-
-    if (!showText) {
-	paint->restore();
-	return;
-    }
-
-    paint->fillRect(QRectF(x0 + 0.5, 0.5, width - 4, fh - 0.5),
-		    QBrush(userColour));
-
-    paint->setPen(QPen(Qt::white));
-
-    QString person = TextAbbrev::abbreviate(m_changeset->authorName(),
-                                            fm, textwid);
-    paint->drawText(x0 + 3, fm.ascent(), person);
-
-    paint->setPen(QPen(Qt::black));
-
-    QStringList tags = m_changeset->tags();
-    if (!tags.empty()) {
-        QStringList nonTipTags;
-        foreach (QString t, tags) {
-            // I'm not convinced that showing the tip tag really
-            // works; I think perhaps it confuses as much as it
-            // illuminates.  But also, our current implementation
-            // doesn't interact well with it because it moves -- it's
-            // the one thing that can actually (in normal use) change
-            // inside an existing changeset record even during an
-            // incremental update
-            if (t != "tip") nonTipTags.push_back(t);
-        }
-        if (!nonTipTags.empty()) {
-            QString tagText = nonTipTags.join(" ").trimmed();
-            int tw = fm.width(tagText);
-            paint->fillRect(QRectF(x0 + width - 8 - tw, 1, tw + 4, fh - 1),
-                            QBrush(Qt::yellow));
-            paint->drawText(x0 + width - 6 - tw, fm.ascent(), tagText);
-        }
-    }
-
-    if (m_showBranch) {
-	// write branch name
-        paint->save();
-	f.setBold(true);
-	paint->setFont(f);
-	paint->setPen(QPen(branchColour));
-	QString branch = m_changeset->branch();
-        if (branch == "") branch = "default";
-	int wid = width - 3;
-	branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid);
-	paint->drawText(x0, -fh + fm.ascent() - 4, branch);
-	f.setBold(false);
-        paint->restore();
-    }
-
-    paint->setFont(f);
-
-    for (int i = 0; i < lines.size(); ++i) {
-	paint->drawText(x0 + 3, i * fh + fh + fm.ascent(), lines[i].trimmed());
-    }
-
-    paint->restore();
-}
--- a/changesetitem.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 CHANGESETITEM_H
-#define CHANGESETITEM_H
-
-#include <QGraphicsObject>
-#include <QFont>
-
-class Changeset;
-class ChangesetDetailItem;
-
-class QAction;
-
-class ChangesetItem : public QGraphicsObject
-{
-    Q_OBJECT
-
-public:
-    ChangesetItem(Changeset *cs);
-
-    virtual QRectF boundingRect() const;
-    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
-
-    Changeset *getChangeset() { return m_changeset; }
-    QString getId();
-
-    int column() const { return m_column; }
-    int row() const { return m_row; }
-    void setColumn(int c) { m_column = c; setX(c * 100); }
-    void setRow(int r) { m_row = r; setY(r * 90); }
-
-    bool isWide() const { return m_wide; }
-    void setWide(bool w) { m_wide = w; }
-
-    bool isCurrent() const { return m_current; }
-    void setCurrent(bool c) { m_current = c; }
-
-    bool isNew() const { return m_new; }
-    void setNew(bool n) { m_new = n; }
-
-    bool showBranch() const { return m_showBranch; }
-    void setShowBranch(bool s) { m_showBranch = s; }
-
-signals:
-    void detailShown();
-    void detailHidden();
-
-    void updateTo(QString);
-    void diffToCurrent(QString);
-    void diffToParent(QString child, QString parent);
-    void showSummary(Changeset *);
-    void mergeFrom(QString);
-    void newBranch(QString);
-    void tag(QString);
-
-public slots:
-    void showDetail();
-    void hideDetail();
-
-private slots:
-    void copyIdActivated();
-    void updateActivated();
-    void diffToParentActivated();
-    void showSummaryActivated();
-    void diffToCurrentActivated();
-    void mergeActivated();
-    void tagActivated();
-    void newBranchActivated();
-
-protected:
-    virtual void mousePressEvent(QGraphicsSceneMouseEvent *);
-
-private:
-    void activateMenu();
-
-    QFont m_font;
-    Changeset *m_changeset;
-    ChangesetDetailItem *m_detail;
-    bool m_showBranch;
-    int m_column;
-    int m_row;
-    bool m_wide;
-    bool m_current;
-    bool m_new;
-
-    QMap<QAction *, QString> m_parentDiffActions;
-    QMap<QAction *, QString> m_summaryActions;
-};
-
-#endif // CHANGESETITEM_H
--- a/changesetscene.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "changesetscene.h"
-#include "changesetitem.h"
-#include "uncommitteditem.h"
-#include "dateitem.h"
-
-ChangesetScene::ChangesetScene()
-    : QGraphicsScene(), m_detailShown(0)
-{
-}
-
-void
-ChangesetScene::addChangesetItem(ChangesetItem *item)
-{
-    addItem(item);
-
-    connect(item, SIGNAL(detailShown()),
-            this, SLOT(changesetDetailShown()));
-
-    connect(item, SIGNAL(detailHidden()),
-            this, SLOT(changesetDetailHidden()));
-
-    connect(item, SIGNAL(updateTo(QString)),
-            this, SIGNAL(updateTo(QString)));
-
-    connect(item, SIGNAL(diffToCurrent(QString)),
-            this, SIGNAL(diffToCurrent(QString)));
-
-    connect(item, SIGNAL(diffToParent(QString, QString)),
-            this, SIGNAL(diffToParent(QString, QString)));
-
-    connect(item, SIGNAL(showSummary(Changeset *)),
-            this, SIGNAL(showSummary(Changeset *)));
-
-    connect(item, SIGNAL(mergeFrom(QString)),
-            this, SIGNAL(mergeFrom(QString)));
-
-    connect(item, SIGNAL(newBranch(QString)),
-            this, SIGNAL(newBranch(QString)));
-
-    connect(item, SIGNAL(tag(QString)),
-            this, SIGNAL(tag(QString)));
-}
-
-void
-ChangesetScene::addUncommittedItem(UncommittedItem *item)
-{
-    addItem(item);
-    
-    connect(item, SIGNAL(commit()),
-            this, SIGNAL(commit()));
-    
-    connect(item, SIGNAL(revert()),
-            this, SIGNAL(revert()));
-    
-    connect(item, SIGNAL(diff()),
-            this, SIGNAL(diffWorkingFolder()));
-
-    connect(item, SIGNAL(showSummary()),
-            this, SIGNAL(showSummary()));
-
-    connect(item, SIGNAL(showWork()),
-            this, SIGNAL(showWork()));
-
-    connect(item, SIGNAL(newBranch()),
-            this, SIGNAL(newBranch()));
-
-    connect(item, SIGNAL(noBranch()),
-            this, SIGNAL(noBranch()));
-
-}
-
-void
-ChangesetScene::addDateItem(DateItem *item)
-{
-    addItem(item);
-
-    connect(item, SIGNAL(clicked()),
-            this, SLOT(dateItemClicked()));
-}
-
-void
-ChangesetScene::changesetDetailShown()
-{
-    ChangesetItem *csi = qobject_cast<ChangesetItem *>(sender());
-    if (!csi) return;
-
-    if (m_detailShown && m_detailShown != csi) {
-	m_detailShown->hideDetail();
-    }
-    m_detailShown = csi;
-}
-
-void
-ChangesetScene::changesetDetailHidden()
-{
-    m_detailShown = 0;
-}
-
-void
-ChangesetScene::dateItemClicked()
-{
-    if (m_detailShown) {
-        m_detailShown->hideDetail();
-    }
-}
-
-ChangesetItem *
-ChangesetScene::getItemById(QString id)
-{
-    foreach (QGraphicsItem *it, items()) {
-        ChangesetItem *csit = dynamic_cast<ChangesetItem *>(it);
-        if (csit && csit->getId() == id) return csit;
-    }
-    return 0;
-}
-
-
--- a/changesetscene.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 CHANGESETSCENE_H
-#define CHANGESETSCENE_H
-
-#include <QGraphicsScene>
-
-class ChangesetItem;
-class Changeset;
-class UncommittedItem;
-class DateItem;
-
-class ChangesetScene : public QGraphicsScene
-{
-    Q_OBJECT
-
-public:
-    ChangesetScene();
-
-    void addChangesetItem(ChangesetItem *item);
-    void addUncommittedItem(UncommittedItem *item);
-    void addDateItem(DateItem *item);
-
-    ChangesetItem *getItemById(QString id); // Slow: traversal required
-
-signals:
-    void commit();
-    void revert();
-    void diffWorkingFolder();
-    void showSummary();
-    void showWork();
-    void newBranch();
-    void noBranch();
-
-    void updateTo(QString id);
-    void diffToParent(QString id, QString parent);
-    void showSummary(Changeset *);
-    void diffToCurrent(QString id);
-    void mergeFrom(QString id);
-    void newBranch(QString id);
-    void tag(QString id);
-
-private slots:
-    void changesetDetailShown();
-    void changesetDetailHidden();
-    void dateItemClicked();
-
-private:
-    ChangesetItem *m_detailShown;
-};
-
-#endif
--- a/clickablelabel.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 _CLICKABLE_LABEL_H_
-#define _CLICKABLE_LABEL_H_
-
-#include <QLabel>
-
-class ClickableLabel : public QLabel
-{
-    Q_OBJECT
-
-    Q_PROPERTY(bool mouseUnderline READ mouseUnderline WRITE setMouseUnderline)
-
-public:
-    ClickableLabel(const QString &text, QWidget *parent = 0) :
-        QLabel(text, parent),
-	m_naturalText(text)
-    { }
-
-    ClickableLabel(QWidget *parent = 0) :
-	QLabel(parent)
-    { }
-
-    ~ClickableLabel()
-    { }
-
-    void setText(const QString &t) {
-	m_naturalText = t;
-	QLabel::setText(t);
-    }
-
-    bool mouseUnderline() const {
-	return m_mouseUnderline;
-    }
-
-    void setMouseUnderline(bool mu) {
-	m_mouseUnderline = mu;
-	if (mu) {
-	    setTextFormat(Qt::RichText);
-	    setCursor(Qt::PointingHandCursor);
-	}
-    }
-
-signals:
-    void clicked();
-
-protected:
-    virtual void enterEvent(QEvent *) {
-	if (m_mouseUnderline) {
-	    QLabel::setText(tr("<u>%1</u>").arg(m_naturalText));
-	}
-    }
-
-    virtual void leaveEvent(QEvent *) {
-	if (m_mouseUnderline) {
-	    QLabel::setText(m_naturalText);
-	}
-    }
-
-    virtual void mousePressEvent(QMouseEvent *) {
-        emit clicked();
-    }
-
-private:
-    bool m_mouseUnderline;
-    QString m_naturalText;
-};
-
-#endif
--- a/colourset.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "colourset.h"
-
-ColourSet
-ColourSet::m_instance;
-
-ColourSet::ColourSet() { }
-
-ColourSet *
-ColourSet::instance()
-{
-    return &m_instance;
-}
-
-QColor
-ColourSet::getColourFor(QString n)
-{
-    if (m_defaultNames.contains(n)) return Qt::black;
-    if (m_colours.contains(n)) return m_colours[n];
-
-    QColor c;
-
-    if (m_colours.empty()) {
-	c = QColor::fromHsv(0, 200, 100);
-    } else {
-	c = QColor::fromHsv((m_lastColour.hue() + 70) % 360, 200, 100);
-    }
-
-    m_colours[n] = c;
-    m_lastColour = c;
-    return c;
-}
-
-
-    
--- a/colourset.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 _COLOURSET_H_
-#define _COLOURSET_H_
-
-#include <QSet>
-#include <QMap>
-#include <QColor>
-#include <QString>
-
-class ColourSet
-{
-public:
-    void clearDefaultNames() { m_defaultNames.clear(); }
-    void addDefaultName(QString n) { m_defaultNames.insert(n); }
-
-    QColor getColourFor(QString n);
-
-    static ColourSet *instance();
-
-private:
-    ColourSet();
-    QSet<QString> m_defaultNames;
-    QMap<QString, QColor> m_colours;
-    QColor m_lastColour;
-
-    static ColourSet m_instance;
-};
-
-#endif
--- a/common.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,259 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "common.h"
-#include "debug.h"
-
-#include <QFileInfo>
-#include <QProcessEnvironment>
-#include <QStringList>
-#include <QDir>
-#include <QRegExp>
-
-#include <sys/types.h>
-
-#ifdef Q_OS_WIN32
-#define _WIN32_WINNT 0x0500
-#define SECURITY_WIN32 
-#include <windows.h>
-#include <security.h>
-#else
-#include <errno.h>
-#include <pwd.h>
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <signal.h>
-#endif
-
-QString findInPath(QString name, QString installPath, bool executableRequired)
-{
-    bool found = false;
-    if (name != "") {
-        if (name[0] != '/'
-#ifdef Q_OS_WIN32
-            && (QRegExp("^\\w:").indexIn(name) != 0)
-#endif
-            ) {
-#ifdef Q_OS_WIN32
-            QChar pathSep = ';';
-#else
-            QChar pathSep = ':';
-#endif
-            name = QFileInfo(name).fileName();
-            QString path =
-                QProcessEnvironment::systemEnvironment().value("PATH");
-            DEBUG << "findInPath: seeking location for binary " << name
-                  << ": system path is " << path << endl;
-            if (installPath != "") {   
-                DEBUG << "findInPath: install path is " << installPath
-                      << ", adding to system path" << endl;
-                //!!! path = path + pathSep + installPath;
-                path = installPath + pathSep + path;
-            }
-#ifndef Q_OS_WIN32
-            //!!!
-            path = path + ":/usr/local/bin";
-            DEBUG << "... adding /usr/local/bin just in case (fix and add settings dlg please)"
-                    << endl;
-#endif
-            QStringList elements = path.split(pathSep, QString::SkipEmptyParts);
-            foreach (QString element, elements) {
-                QString full = QDir(element).filePath(name);
-                QFileInfo fi(full);
-                DEBUG << "findInPath: looking at " << full << endl;
-                if (fi.exists() && fi.isFile()) {
-                    DEBUG << "findInPath: it's a file" << endl;
-                    if (!executableRequired || fi.isExecutable()) {
-                        name = full;
-                        DEBUG << "findInPath: found at " << name << endl;
-                        found = true;
-                        break;
-                    }
-                }
-            }
-        } else {
-            // absolute path given
-            QFileInfo fi(name);
-            DEBUG << "findInPath: looking at absolute path " << name << endl;
-            if (fi.exists() && fi.isFile()) {
-                DEBUG << "findInPath: it's a file" << endl;
-                if (!executableRequired || fi.isExecutable()) {
-                    DEBUG << "findInPath: found at " << name << endl;
-                    found = true;
-                }
-            }
-        }
-    }
-#ifdef Q_OS_WIN32
-    if (!found) {
-        if (!name.endsWith(".exe")) {
-            return findInPath(name + ".exe", installPath, executableRequired);
-        }
-    }
-#endif
-    if (found) {
-        return name;
-    } else {
-        return "";
-    }
-}
-
-#ifdef Q_OS_WIN32
-QString getUserRealName()
-{
-    TCHAR buf[1024];
-    long unsigned int maxlen = 1000;
-    LPTSTR info = buf;
-
-    if (!GetUserNameEx(NameDisplay, info, &maxlen)) {
-        DEBUG << "GetUserNameEx failed: " << GetLastError() << endl;
-        return "";
-    }
-
-#ifdef UNICODE
-    return QString::fromUtf16((const unsigned short *)info);
-#else
-    return QString::fromLocal8Bit(info);
-#endif
-}
-#else
-#ifdef Q_OS_MAC
-// Nothing here: definition is in common_osx.mm
-#else
-QString getUserRealName()
-{
-    const int maxlen = 1023;
-    char buf[maxlen + 2];
-
-    if (getlogin_r(buf, maxlen)) return "";
-
-    struct passwd *p = getpwnam(buf);
-    if (!p) return "";
-    
-    QString s(p->pw_gecos);
-    if (s != "") s = s.split(',')[0];
-    return s;
-}
-#endif
-#endif
-
-void loseControllingTerminal()
-{
-#ifndef Q_OS_WIN32
-
-    if (!isatty(0)) {
-        DEBUG << "stdin is not a terminal" << endl;
-    } else {
-        DEBUG << "stdin is a terminal, detaching from it" << endl;
-        if (ioctl(0, TIOCNOTTY, NULL) < 0) {
-            perror("ioctl failed");
-            DEBUG << "ioctl for TIOCNOTTY on stdin failed (errno = " << errno << ")" << endl;
-        } else {
-            DEBUG << "ioctl for TIOCNOTTY on stdin succeeded" << endl;
-	    return;
-        }
-    }
-
-    int ttyfd = open("/dev/tty", O_RDWR);
-    if (ttyfd < 0) {
-        DEBUG << "failed to open controlling terminal" << endl;
-    } else {
-        if (ioctl(ttyfd, TIOCNOTTY, NULL) < 0) {
-            perror("ioctl failed");
-            DEBUG << "ioctl for TIOCNOTTY on controlling terminal failed (errno = " << errno << ")" << endl;
-        } else {
-            DEBUG << "ioctl for TIOCNOTTY on controlling terminal succeeded" << endl;
-	    return;
-        }
-    }
-
-#endif
-}
-
-void installSignalHandlers()
-{
-#ifndef Q_OS_WIN32
-    sigset_t sgnals;
-    sigemptyset (&sgnals);
-    sigaddset(&sgnals, SIGHUP);
-    sigaddset(&sgnals, SIGCONT);
-    pthread_sigmask(SIG_BLOCK, &sgnals, 0);
-#endif
-}
-
-FolderStatus getFolderStatus(QString path)
-{
-    if (path != "/" && path.endsWith("/")) {
-        path = path.left(path.length()-1);
-    }
-    DEBUG << "getFolderStatus: " << path << endl;
-    QFileInfo fi(path);
-    if (fi.exists()) {
-        DEBUG << "exists" << endl;
-        QDir dir(path);
-        if (!dir.exists()) { // returns false for files
-            DEBUG << "not directory" << endl;
-            return FolderIsFile;
-        }
-        if (QDir(dir.filePath(".hg")).exists()) {
-            DEBUG << "has repo" << endl;
-            return FolderHasRepo;
-        }
-        return FolderExists;
-    } else {
-        QDir parent = fi.dir();
-        if (parent.exists()) {
-            DEBUG << "parent exists" << endl;
-            return FolderParentExists;
-        }
-        return FolderUnknown;
-    }
-}
-
-QString getContainingRepoFolder(QString path)
-{
-    if (getFolderStatus(path) == FolderHasRepo) return "";
-
-    QFileInfo me(path);
-    QFileInfo parent(me.dir().absolutePath());
-
-    while (me != parent) {
-        QString parentPath = parent.filePath();
-        if (getFolderStatus(parentPath) == FolderHasRepo) {
-            return parentPath;
-        }
-        me = parent;
-        parent = me.dir().absolutePath();
-    }
-
-    return "";
-}
-
-QString xmlEncode(QString s)
-{
-    s
-	.replace("&", "&amp;")
-	.replace("<", "&lt;")
-	.replace(">", "&gt;")
-	.replace("\"", "&quot;")
-	.replace("'", "&apos;");
-
-    return s;
-}
--- a/common.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 COMMON_H
-#define COMMON_H
-
-#include <QString>
-
-#define MY_ICON_SIZE                    32
-//!!!:
-#define REPOMENU_TITLE                  "Repository actions"
-#define WORKFOLDERMENU_TITLE            "Workfolder actions"
-
-extern QString findInPath(QString name, QString installPath, bool executable);
-
-extern QString getSystem();
-extern QString getHgDirName();
-
-extern QString getUserRealName();
-
-extern void loseControllingTerminal();
-
-void installSignalHandlers();
-
-/**
- * Status used in testing whether a folder argument (received from the
- * user) is valid for particular uses.
- */
-enum FolderStatus {
-    FolderUnknown,      /// Neither the folder nor its parent exists
-    FolderParentExists, /// The folder is absent, but its parent exists
-    FolderExists,       /// The folder exists and has no .hg repo in it
-    FolderHasRepo,      /// The folder exists and has an .hg repo in it
-    FolderIsFile        /// The "folder" is actually a file
-};
-
-FolderStatus getFolderStatus(QString path);
-
-/**
- * If the given path is somewhere within an existing repository,
- * return the path of the root directory of the repository (i.e. the
- * one with .hg in it).
- *
- * If the given path is _not_ in a repository, or the given path _is_
- * the root directory of a repository, return QString().  Use
- * getFolderStatus to distinguish between these cases.
- */
-QString getContainingRepoFolder(QString path);
-
-QString xmlEncode(QString);
-    
-
-#endif 	//COMMON_H
-
-
--- a/common_osx.mm	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-
-#include "common.h"
-
-#include <QString>
-
-#ifdef Q_OS_MAC
-
-#include <Foundation/Foundation.h>
-
-QString getUserRealName()
-{
-    NSString *s = NSFullUserName();
-    return QString::fromLocal8Bit([s UTF8String]);
-}
-
-#endif
--- a/confirmcommentdialog.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,257 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 "confirmcommentdialog.h"
-#include "common.h"
-
-#include <QMessageBox>
-#include <QInputDialog>
-#include <QGridLayout>
-#include <QLabel>
-#include <QTextEdit>
-#include <QDialogButtonBox>
-
-ConfirmCommentDialog::ConfirmCommentDialog(QWidget *parent,
-                                           QString title,
-                                           QString introText,
-                                           QString initialComment,
-                                           QString okButtonText) :
-    QDialog(parent)
-{
-    setWindowTitle(title);
-
-    QGridLayout *layout = new QGridLayout;
-    setLayout(layout);
-    QLabel *label = new QLabel(introText);
-    label->setWordWrap(true);
-    layout->addWidget(label, 0, 0);
-
-    m_textEdit = new QTextEdit;
-    m_textEdit->setAcceptRichText(false);
-    m_textEdit->document()->setPlainText(initialComment);
-    m_textEdit->setMinimumWidth(360);
-    connect(m_textEdit, SIGNAL(textChanged()), this, SLOT(commentChanged()));
-    layout->addWidget(m_textEdit, 1, 0);
-
-    QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok |
-                                                  QDialogButtonBox::Cancel);
-    layout->addWidget(bbox, 2, 0);
-    m_ok = bbox->button(QDialogButtonBox::Ok);
-    m_ok->setDefault(true);
-    m_ok->setEnabled(initialComment != "");
-    m_ok->setText(okButtonText);
-    bbox->button(QDialogButtonBox::Cancel)->setAutoDefault(false);
-
-    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
-    connect(bbox, SIGNAL(rejected()), this, SLOT(reject()));
-}
-
-void ConfirmCommentDialog::commentChanged()
-{
-    m_ok->setEnabled(getComment() != "");
-}
-
-QString ConfirmCommentDialog::getComment() const
-{
-    return m_textEdit->document()->toPlainText();
-}
-
-QString ConfirmCommentDialog::buildFilesText(QString intro, QStringList files)
-{
-    QString text;
-
-    if (intro == "") text = "<qt>";
-    else text = "<qt>" + intro + "<p>";
-
-    text += "<code>";
-    foreach (QString file, files) {
-        text += "&nbsp;&nbsp;&nbsp;" + xmlEncode(file) + "<br>";
-    }
-    text += "</code></qt>";
-
-    return text;
-}
-
-bool ConfirmCommentDialog::confirm(QWidget *parent,
-                                   QString title,
-                                   QString head,
-                                   QString text,
-                                   QString okButtonText)
-{
-    QMessageBox box(QMessageBox::Question,
-                    title,
-                    head,
-                    QMessageBox::Cancel,
-                    parent);
-
-    box.setInformativeText(text);
-
-    QPushButton *ok = box.addButton(QMessageBox::Ok);
-    ok->setText(okButtonText);
-    box.setDefaultButton(QMessageBox::Ok);
-    if (box.exec() == -1) return false;
-    return box.standardButton(box.clickedButton()) == QMessageBox::Ok;
-}
-
-bool ConfirmCommentDialog::confirmDangerous(QWidget *parent,
-                                            QString title,
-                                            QString head,
-                                            QString text,
-                                            QString okButtonText)
-{
-    QMessageBox box(QMessageBox::Warning,
-                    title,
-                    head,
-                    QMessageBox::Cancel,
-                    parent);
-
-    box.setInformativeText(text);
-
-    QPushButton *ok = box.addButton(QMessageBox::Ok);
-    ok->setText(okButtonText);
-    box.setDefaultButton(QMessageBox::Cancel);
-    if (box.exec() == -1) return false;
-    return box.standardButton(box.clickedButton()) == QMessageBox::Ok;
-}
-
-bool ConfirmCommentDialog::confirmFilesAction(QWidget *parent,
-                                              QString title,
-                                              QString introText,
-                                              QString introTextWithCount,
-                                              QStringList files,
-                                              QString okButtonText)
-{
-    QString text;
-    if (files.size() <= 10) {
-        text = buildFilesText(introText, files);
-    } else {
-        text = "<qt>" + introTextWithCount + "</qt>";
-    }
-    return confirm(parent, title, text, "", okButtonText);
-}
-
-bool ConfirmCommentDialog::confirmDangerousFilesAction(QWidget *parent,
-                                                       QString title,
-                                                       QString introText,
-                                                       QString introTextWithCount,
-                                                       QStringList files,
-                                                       QString okButtonText)
-{
-    QString text;
-    if (files.size() <= 10) {
-        text = buildFilesText(introText, files);
-    } else {
-        text = "<qt>" + introTextWithCount + "</qt>";
-    }
-    return confirmDangerous(parent, title, text, "", okButtonText);
-}
-
-bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent,
-                                                     QString title,
-                                                     QString introText,
-                                                     QString introTextWithCount,
-                                                     QStringList files,
-                                                     QString &comment,
-                                                     QString okButtonText)
-{
-    return confirmAndComment(parent, title, introText,
-                             introTextWithCount, files, comment, false,
-                             okButtonText);
-}
-
-bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent,
-                                                    QString title,
-                                                    QString introText,
-                                                    QString introTextWithCount,
-                                                    QStringList files,
-                                                    QString &comment,
-                                                    QString okButtonText)
-{
-    return confirmAndComment(parent, title, introText,
-                             introTextWithCount, files, comment, true,
-                             okButtonText);
-}
-
-bool ConfirmCommentDialog::confirmAndComment(QWidget *parent,
-                                             QString title,
-                                             QString introText,
-                                             QString introTextWithCount,
-                                             QStringList files,
-                                             QString &comment,
-                                             bool longComment, 
-                                             QString okButtonText)
-{
-    QString text;
-    if (files.size() <= 10) {
-        text = buildFilesText(introText, files);
-    } else {
-        text = "<qt>" + introTextWithCount;
-    }
-    text += tr("<p>Please enter your comment:</qt>");
-    return confirmAndComment(parent, title, text, comment, longComment,
-                             okButtonText);
-}
-
-bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent,
-                                                     QString title,
-                                                     QString introText,
-                                                     QString &comment,
-                                                     QString okButtonText)
-{
-    return confirmAndComment(parent, title, introText, comment, false,
-                             okButtonText);
-}
-
-bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent,
-                                                    QString title,
-                                                    QString introText,
-                                                    QString &comment,
-                                                    QString okButtonText)
-{
-    return confirmAndComment(parent, title, introText, comment, true,
-                             okButtonText);
-}
-
-bool ConfirmCommentDialog::confirmAndComment(QWidget *parent,
-                                             QString title,
-                                             QString introText,
-                                             QString &comment,
-                                             bool longComment,
-                                             QString okButtonText)
-{
-    bool ok = false;
-    if (!longComment) {
-        QInputDialog d(parent);
-        d.setWindowTitle(title);
-        d.setLabelText(introText);
-        d.setTextValue(comment);
-        d.setOkButtonText(okButtonText);
-        d.setTextEchoMode(QLineEdit::Normal);
-        if (d.exec() == QDialog::Accepted) {
-            comment = d.textValue();
-            ok = true;
-        }
-    } else {
-        ConfirmCommentDialog d(parent, title, introText, comment, okButtonText);
-        if (d.exec() == QDialog::Accepted) {
-            comment = d.getComment();
-            ok = true;
-        }
-    }
-
-    return ok;
-}
--- a/confirmcommentdialog.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 CONFIRMCOMMENTDIALOG_H
-#define CONFIRMCOMMENTDIALOG_H
-
-#include <QDialog>
-#include <QWidget>
-#include <QString>
-#include <QStringList>
-#include <QTextEdit>
-#include <QPushButton>
-
-class ConfirmCommentDialog : public QDialog
-{
-    Q_OBJECT
-
-public:
-    static bool confirm(QWidget *parent,
-                        QString title,
-                        QString head,
-                        QString text,
-                        QString okButtonText);
-    
-    static bool confirmDangerous(QWidget *parent,
-                                 QString title,
-                                 QString head,
-                                 QString text,
-                                 QString okButtonText);
-    
-    static bool confirmFilesAction(QWidget *parent,
-                                   QString title,
-                                   QString introText,
-                                   QString introTextWithCount,
-                                   QStringList files,
-                                   QString okButtonText);
-
-    static bool confirmDangerousFilesAction(QWidget *parent,
-                                            QString title,
-                                            QString introText,
-                                            QString introTextWithCount,
-                                            QStringList files,
-                                            QString okButtonText);
-
-    static bool confirmAndGetShortComment(QWidget *parent,
-                                          QString title,
-                                          QString introText,
-                                          QString introTextWithCount,
-                                          QStringList files,
-                                          QString &comment,
-                                          QString okButtonText);
-
-    static bool confirmAndGetLongComment(QWidget *parent,
-                                         QString title,
-                                         QString introText,
-                                         QString introTextWithCount,
-                                         QStringList files,
-                                         QString &comment,
-                                         QString okButtonText);
-
-    static bool confirmAndGetShortComment(QWidget *parent,
-                                          QString title,
-                                          QString introText,
-                                          QString &comment,
-                                          QString okButtonText);
-
-    static bool confirmAndGetLongComment(QWidget *parent,
-                                         QString title,
-                                         QString introText,
-                                         QString &comment,
-                                         QString okButtonText);
-
-private slots:
-    void commentChanged();
-
-private:
-    ConfirmCommentDialog(QWidget *parent,
-                         QString title,
-                         QString introText,
-                         QString initialComment,
-                         QString okButtonText);
-
-    static bool confirmAndComment(QWidget *parent,
-                                  QString title,
-                                  QString introText,
-                                  QString introTextWithCount,
-                                  QStringList files,
-                                  QString &comment,
-                                  bool longComment,
-                                  QString okButtonText);
-
-    static bool confirmAndComment(QWidget *parent,
-                                  QString title,
-                                  QString introText,
-                                  QString &comment,
-                                  bool longComment,
-                                  QString okButtonText);
-
-    static QString buildFilesText(QString intro, QStringList files);
-
-    QString getComment() const;
-
-    QTextEdit *m_textEdit;
-    QPushButton *m_ok;
-};
-
-#endif // CONFIRMCOMMENTDIALOG_H
--- a/connectionitem.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "connectionitem.h"
-#include "uncommitteditem.h"
-
-#include "changesetitem.h"
-#include "changeset.h"
-#include "colourset.h"
-
-#include <QPainter>
-
-QRectF
-ConnectionItem::boundingRect() const
-{
-    if (!m_parent || !(m_child || m_uncommitted)) return QRectF();
-    float xscale = 100;
-    float yscale = 90;
-    float size = 50;
-
-    int p_col = m_parent->column(), p_row = m_parent->row();
-    int c_col, c_row;
-    if (m_child) {
-        c_col = m_child->column(); c_row = m_child->row();
-    } else {
-        c_col = m_uncommitted->column(); c_row = m_uncommitted->row();
-    }
-
-    return QRectF(xscale * c_col + size/2 - 2,
-		  yscale * c_row + size - 2,
-		  xscale * p_col - xscale * c_col + 4,
-		  yscale * p_row - yscale * c_row - size + 4)
-	.normalized();
-}
-
-void
-ConnectionItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
-{
-    if (!m_parent || !(m_child || m_uncommitted)) return;
-    QPainterPath p;
-
-    paint->save();
-
-    ColourSet *colourSet = ColourSet::instance();
-    QString branch;
-    if (m_child) branch = m_child->getChangeset()->branch();
-    else branch = m_uncommitted->branch();
-    QColor branchColour = colourSet->getColourFor(branch);
-
-    Qt::PenStyle ls = Qt::SolidLine;
-    if (!m_child) ls = Qt::DashLine;
-
-    QTransform t = paint->worldTransform();
-    float scale = std::min(t.m11(), t.m22());
-    if (scale < 0.2) {
-	paint->setPen(QPen(branchColour, 0, ls));
-    } else {
-	paint->setPen(QPen(branchColour, 2, ls));
-    }
-
-    float xscale = 100;
-
-    float yscale = 90;
-    float size = 50;
-    float ygap = yscale - size - 2;
-
-    int p_col = m_parent->column(), p_row = m_parent->row();
-    int c_col, c_row;
-    if (m_child) {
-        c_col = m_child->column(); c_row = m_child->row();
-    } else {
-        c_col = m_uncommitted->column(); c_row = m_uncommitted->row();
-    }
-
-    float c_x = xscale * c_col + size/2;
-    float p_x = xscale * p_col + size/2;
-
-    // ensure line reaches the box, even if it's in a small height --
-    // doesn't matter if we overshoot as the box is opaque and has a
-    // greater Z value
-    p.moveTo(c_x, yscale * c_row + size - 20);
-
-    p.lineTo(c_x, yscale * c_row + size);
-
-    if (p_col == c_col) {
-
-	p.lineTo(p_x, yscale * p_row);
-
-    } else if (m_type == Split || m_type == Normal) {
-
-	// place the bulk of the line on the child (i.e. branch) row
-
-	if (abs(p_row - c_row) > 1) {
-	    p.lineTo(c_x, yscale * p_row - ygap);
-	}
-
-	p.cubicTo(c_x, yscale * p_row,
-		  p_x, yscale * p_row - ygap,
-		  p_x, yscale * p_row);
-
-    } else if (m_type == Merge) {
-
-	// place bulk of the line on the parent row
-
-	p.cubicTo(c_x, yscale * c_row + size + ygap,
-		  p_x, yscale * c_row + size,
-		  p_x, yscale * c_row + size + ygap);
-
-	if (abs(p_row - c_row) > 1) {
-	    p.lineTo(p_x, yscale * p_row);
-	}
-    }
-
-    paint->drawPath(p);
-    paint->restore();
-}
-
-
--- a/connectionitem.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 CONNECTIONITEM_H
-#define CONNECTIONITEM_H
-
-#include <QGraphicsItem>
-
-class Connection;
-
-class ChangesetItem;
-class UncommittedItem;
-
-class ConnectionItem : public QGraphicsItem
-{
-public:
-    enum Type {
-	Normal,
-	Split,
-	Merge
-    };
-
-    ConnectionItem() : m_type(Normal), m_parent(0), m_child(0) { }
-
-    virtual QRectF boundingRect() const;
-    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
-
-    Type connectionType() const { return m_type; }
-    void setConnectionType(Type t) { m_type = t; }
-
-    //!!! 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; }
-    void setChild(UncommittedItem *u) { m_uncommitted = u; }
-
-private:
-    Type m_type;
-    ChangesetItem *m_parent;
-    ChangesetItem *m_child;
-    UncommittedItem *m_uncommitted;
-};
-
-#endif // CONNECTIONITEM_H
--- a/dateitem.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "dateitem.h"
-
-#include "debug.h"
-
-#include <QPainter>
-#include <QBrush>
-#include <QFont>
-#include <QGraphicsSceneMouseEvent>
-
-DateItem::DateItem() :
-    m_minrow(0), m_maxrow(0),
-    m_mincol(0), m_maxcol(0),
-    m_even(false)
-{
-}
-
-void
-DateItem::setRows(int minrow, int n)
-{
-    m_minrow = minrow;
-    m_maxrow = minrow + n - 1;
-    setY(m_minrow * 90);
-}
-
-void
-DateItem::setCols(int mincol, int n)
-{
-    m_mincol = mincol;
-    m_maxcol = mincol + n - 1;
-    setX(m_mincol * 100);
-}
-
-void
-DateItem::mousePressEvent(QGraphicsSceneMouseEvent *e)
-{
-    DEBUG << "DateItem::mousePressEvent" << endl;
-    if (e->button() == Qt::LeftButton) {
-        emit clicked();
-    }
-    e->ignore();
-}
-
-QRectF
-DateItem::boundingRect() const
-{
-    return QRectF(-75, -25,
-		  (m_maxcol - m_mincol + 1) * 100 + 100,
-		  (m_maxrow - m_minrow + 1) * 90).normalized();
-}
-
-void
-DateItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *opt, QWidget *w)
-{
-    QBrush brush;
-
-    if (m_even) {
-	QColor c(QColor::fromRgb(240, 240, 240));
-	brush = QBrush(c);
-    } else {
-	QColor c(QColor::fromRgb(250, 250, 250));
-	brush = QBrush(c);
-    }
-
-    paint->fillRect(boundingRect(), brush);
-
-    paint->save();
-    QFont f(paint->font());
-    f.setBold(true);
-    paint->setFont(f);
-    paint->drawText(-70, -10, m_dateString);
-    paint->restore();
-}
-
-
--- a/dateitem.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 DATEITEM_H
-#define DATEITEM_H
-
-#include <QGraphicsObject>
-
-class DateItem : public QGraphicsObject
-{
-    Q_OBJECT
-
-public:
-    DateItem();
-
-    virtual QRectF boundingRect() const;
-    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
-
-    void setRows(int minrow, int n);
-    void setCols(int mincol, int n);
-
-    void setEven(bool e) { m_even = e; }
-
-    QString dateString() const { return m_dateString; }
-    void setDateString(QString s) { m_dateString = s; }
-
-signals:
-    void clicked();
-
-protected:
-    virtual void mousePressEvent(QGraphicsSceneMouseEvent *);
-
-private:
-    QString m_dateString;
-    int m_minrow;
-    int m_maxrow;
-    int m_mincol;
-    int m_maxcol;
-    bool m_even;
-};
-
-#endif // DATEITEM_H
--- a/debug.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "debug.h"
-
-#include <QString>
-#include <QUrl>
-#include <QMutex>
-#include <QMutexLocker>
-#include <QFile>
-#include <QDir>
-#include <QCoreApplication>
-#include <QDateTime>
-
-#include <cstdio>
-
-QDebug &
-getEasyHgDebug()
-{
-    static QFile *logFile = 0;
-    static QDebug *debug = 0;
-    static QMutex mutex;
-    static char *prefix;
-    mutex.lock();
-    if (!debug) {
-        prefix = new char[20];
-        sprintf(prefix, "[%lu]", (unsigned long)QCoreApplication::applicationPid());
-        logFile = new QFile(QDir::homePath() + "/.easyhg.log");
-        if (logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
-            QDebug(QtDebugMsg) << (const char *)prefix
-                               << "Opened debug log file "
-                               << logFile->fileName();
-            debug = new QDebug(logFile);
-        } else {
-            QDebug(QtWarningMsg) << (const char *)prefix
-                                 << "Failed to open debug log file "
-                                 << logFile->fileName()
-                                 << " for writing, using console debug instead";
-            debug = new QDebug(QtDebugMsg);
-            delete logFile;
-            logFile = 0;
-        }
-        *debug << endl << (const char *)prefix << "Log started at "
-               << QDateTime::currentDateTime().toString();
-    }
-    mutex.unlock();
-
-    QDebug &dref = *debug;
-    return dref << endl << (const char *)prefix;
-}
-
-QDebug &
-operator<<(QDebug &dbg, const std::string &s)
-{
-    dbg << QString::fromUtf8(s.c_str());
-    return dbg;
-}
-
-std::ostream &
-operator<<(std::ostream &target, const QString &str)
-{
-    return target << str.toLocal8Bit().data();
-}
-
-std::ostream &
-operator<<(std::ostream &target, const QUrl &u)
-{
-    return target << "<" << u.toString() << ">";
-}
-
--- a/debug.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 _DEBUG_H_
-#define _DEBUG_H_
-
-#include <QDebug>
-#include <QTextStream>
-#include <string>
-#include <iostream>
-
-class QString;
-class QUrl;
-
-QDebug &operator<<(QDebug &, const std::string &);
-std::ostream &operator<<(std::ostream &, const QString &);
-std::ostream &operator<<(std::ostream &, const QUrl &);
-
-#ifndef NDEBUG
-
-extern QDebug &getEasyHgDebug();
-
-#define DEBUG getEasyHgDebug()
-
-template <typename T>
-inline QDebug &operator<<(QDebug &d, const T &t) {
-    QString s;
-    QTextStream ts(&s);
-    ts << t;
-    d << s;
-    return d;
-}
-
-#else
-
-class NoDebug
-{
-public:
-    inline NoDebug() {}
-    inline ~NoDebug(){}
-
-    template <typename T>
-    inline NoDebug &operator<<(const T &) { return *this; }
-
-    inline NoDebug &operator<<(QTextStreamFunction) { return *this; }
-};
-
-#define DEBUG NoDebug()
-
-#endif /* !NDEBUG */
-
-#endif /* !_DEBUG_H_ */
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/linux/EasyMercurial.desktop	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Encoding=UTF-8
+Type=Application
+Categories=Development
+Exec=EasyMercurial %f
+MimeType=
+X-KDE-NativeMimeType=
+Icon=easyhg-icon
+Comment=A simple user interface for the Mercurial version-control system
+Terminal=false
+Name=EasyMercurial
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/linux/control	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,7 @@
+Package: easymercurial
+Priority: optional
+Maintainer: Chris Cannam <cannam@all-day-breakfast.com>
+Architecture: amd64
+Version: 0.9.8cc-1
+Depends: mercurial (>= 1.7.0),libqtgui4 (>= 4.6.0),libqt4-network,python-crypto,python-qt4
+Description: A simple user interface for the Mercurial version-control system
--- a/deploy/osx/Info.plist	Tue Mar 15 12:36:26 2011 +0000
+++ b/deploy/osx/Info.plist	Thu Dec 06 13:54:34 2018 +0000
@@ -9,10 +9,16 @@
 	<key>CFBundleExecutable</key>
 	<string>EasyMercurial</string>
 	<key>CFBundleIconFile</key>
-	<string>easyhg.icns</string>
+	<string>easyhg-icon.icns</string>
 	<key>CFBundleIdentifier</key>
 	<string>org.easymercurial.EasyMercurial</string>
 	<key>CFBundleShortVersionString</key>
 	<string>EASYHG_VERSION</string>
+
+	<!-- enable HiDPI -->
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+	<key>NSHighResolutionCapable</key>
+	<string>True</string>
 </dict>
 </plist>
--- a/deploy/osx/combine.sh	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-#!/bin/bash
-app=EasyMercurial
-if [ ! -d "$app.carbon.app" ] || [ ! -d "$app.cocoa.app" ]; then
-	echo Carbon or Cocoa bundle not found
-	exit 1
-fi
-(cd "$app.cocoa.app" ; find . -type f -print) | while read f; do
-        d=$(dirname "$f")
-        mkdir -p "$app.app/$d"
-	case $(file "$app.cocoa.app/$f") in
-	*universal*x86_64*)
-		lipo "$app.cocoa.app/$f" -extract x86_64 -output "/tmp/$$.x86_64";;
-	*x86_64*)
-		lipo "$app.cocoa.app/$f" -create -output "/tmp/$$.x86_64";;
-	*)
-		cp "$app.cocoa.app/$f" "$app.app/$f"
-		continue;;
-	esac
-	case $(file "$app.carbon.app/$f") in
-	*x86_64*)
-		lipo "$app.carbon.app/$f" -remove x86_64 -output "/tmp/$$.rest"
-		;;
-	*universal*)
-		cp "$app.carbon.app/$f" "/tmp/$$.rest"
-		;;
-	*)
-		lipo "$app.carbon.app/$f" -create -output "/tmp/$$.rest"
-		;;
-	esac
-	lipo "/tmp/$$.x86_64" "/tmp/$$.rest" -create -output "$app.app/$f"
-	rm "/tmp/$$".*
-done
-
-
--- a/deploy/osx/deploy.sh	Tue Mar 15 12:36:26 2011 +0000
+++ b/deploy/osx/deploy.sh	Thu Dec 06 13:54:34 2018 +0000
@@ -1,11 +1,26 @@
 #!/bin/bash
 
+set -e
+
 # Execute this from the top-level directory of the project (the one
-# that contains the .app bundle).
+# that contains the .app bundle).  Supply the name of the .app bundle
+# as argument (the target will use $app.app regardless, but we need
+# to know the source)
 
+source="$1"
+dmg="$2"
+if [ -z "$source" ] || [ ! -d "$source" ] || [ -z "$dmg" ]; then
+	echo "Usage: $0 <source.app> <target-dmg-basename>"
+	echo "  e.g. $0 MyApplication.app MyApplication"
+ 	echo "  Version number and .dmg will be appended automatically,"
+        echo "  but the .app name must include .app"
+	exit 2
+fi
 app=EasyMercurial
 
-version=`perl -p -e 's/^[^"]*"([^"]*)".*$/$1/' version.h`
+set -u
+
+version=`perl -p -e 's/^[^"]*"([^"]*)".*$/$1/' src/version.h`
 case "$version" in
     [0-9].[0-9]) bundleVersion="$version".0 ;;
     [0-9].[0-9].[0-9]) bundleVersion="$version" ;;
@@ -13,23 +28,56 @@
 esac
 
 echo
+echo "Copying in frameworks and plugins from Qt installation directory."
+
+deploy/osx/copy-qt.sh "$app" || exit 2
+
+echo
+echo "Fixing up paths."
+
+deploy/osx/paths.sh "$app"
+
+echo
+echo "Copying in qt.conf to set local-only plugin paths."
+echo "Make sure all necessary Qt plugins are in $source/Contents/plugins/*"
+echo "You probably want platforms/, accessible/ and imageformats/ subdirectories."
+cp deploy/osx/qt.conf "$source"/Contents/Resources/qt.conf
+
 echo "Writing version $bundleVersion in to bundle."
 echo "(This should be a three-part number: major.minor.point)"
 
 perl -p -e "s/EASYHG_VERSION/$bundleVersion/" deploy/osx/Info.plist \
-    > "$app".app/Contents/Info.plist
+    > "$source"/Contents/Info.plist
 
-echo "Done: check $app.app/Contents/Info.plist for sanity please"
+echo "Done: check $source/Contents/Info.plist for sanity please"
 
-bash deploy/osx/paths.sh "$app"
+echo
+echo "Copying in helper scripts"
+cp easyhg-extdiff.sh "$source"/Contents/MacOS/
+cp easyhg-merge.sh "$source"/Contents/MacOS/
+chmod +x "$source"/Contents/MacOS/easyhg-extdiff.sh "$source"/Contents/MacOS/easyhg-merge.sh
+
+echo
+echo "Making target tree."
+
+volume="$app"-"$version"
+target="$volume"/"$app".app
+dmg="$dmg"-"$version".dmg
+
+mkdir "$volume" || exit 1
+
+ln -s /Applications "$volume"/Applications
+cp COPYING "$volume/COPYING.txt"
+cp -rp "$source" "$target"
+
+echo "Done"
+
+deploy/osx/sign.sh "$volume" || exit 1
 
 echo
 echo "Making dmg..."
 
-mkdir "$app"-"$version" &&
-	ln -s /Applications "$app"-"$version"/Applications &&
-	cp -rp "$app".app "$app"-"$version"/ &&
-	hdiutil create -srcfolder "$app"-"$version" "$app"-"$version".dmg -volname "$app"-"$version" && 
-	rm -r "$app"-"$version"
+hdiutil create -srcfolder "$volume" "$dmg" -volname "$volume" && 
+	rm -r "$volume"
 
 echo "Done"
--- a/deploy/osx/paths.sh	Tue Mar 15 12:36:26 2011 +0000
+++ b/deploy/osx/paths.sh	Thu Dec 06 13:54:34 2018 +0000
@@ -1,5 +1,7 @@
 #!/bin/bash
 
+set -e
+
 app="$1"
 if [ -z "$app" ]; then
 	echo "Usage: $0 <appname>"
@@ -7,28 +9,68 @@
 	exit 2
 fi
 
+set -u
+
+frameworks="QtCore QtNetwork QtGui QtWidgets QtPrintSupport QtDBus"
+
 echo
-echo "I expect you to have already copied QtCore, QtNetwork and QtGui to "
-echo "$app.app/Contents/Frameworks and PyQt4/QtCore.so and PyQt4/QtGui.so to "
-echo "$app.app/Contents/MacOS -- expect errors to follow if they're missing"
+echo "I expect you to have already copied these frameworks from the Qt installation to"
+echo "$app.app/Contents/Frameworks -- expect errors to follow if they're missing:"
+echo "$frameworks"
 echo
 
 echo "Fixing up loader paths in binaries..."
 
-install_name_tool -id QtCore "$app.app/Contents/Frameworks/QtCore"
-install_name_tool -id QtGui "$app.app/Contents/Frameworks/QtGui"
-install_name_tool -id QtNetwork "$app.app/Contents/Frameworks/QtNetwork"
+for fwk in $frameworks; do
+    install_name_tool -id $fwk "$app.app/Contents/Frameworks/$fwk"
+done
 
-for fwk in QtCore QtGui QtNetwork; do
-        find "$app.app" -type f -print | while read x; do
-                current=$(otool -L "$x" | grep "$fwk.framework/" | awk '{ print $1; }')
-                [ -z "$current" ] && continue
-                echo "$x has $current"
-                relative=$(echo "$x" | sed -e "s,$app.app/Contents/,," \
-                        -e 's,[^/]*/,../,g' -e 's,/[^/]*$,/Frameworks/'"$fwk"',' )
-                echo "replacing with relative path $relative"
-                install_name_tool -change "$current" "@loader_path/$relative" "$x"
-        done
+find "$app.app" -name \*.dylib -print | while read x; do
+    install_name_tool -id "`basename \"$x\"`" "$x"
+done
+
+for fwk in $frameworks; do
+    find "$app.app" -type f -print | while read x; do
+
+        current=$(otool -L "$x" | 
+	    grep "$fwk" | grep amework | grep -v ':$' | 
+	    awk '{ print $1; }')
+	
+        [ -z "$current" ] && continue
+	
+        echo "$x has $current"
+	
+	case "$x" in
+	    *PyQt4*)
+		# These are "special" Qt4 libraries used by
+		# the PyQt module. They need to refer to their
+		# own local neighbours. Ugh, but let's handle
+		# those manually here.
+		relative="$fwk.so"
+		;;
+	    *)
+		# The normal Qt5 case
+		relative=$(echo "$x" | 
+		    sed -e "s,$app.app/Contents/,," \
+			-e 's,[^/]*/,../,g' \
+			-e 's,/[^/]*$,/Frameworks/'"$fwk"',' )
+		;;
+	esac
+	
+	echo "replacing with relative path $relative"
+	install_name_tool -change \
+	    "$current" "@loader_path/$relative" "$x"
+    done
+done
+
+find "$app.app" -type f -print | while read x; do
+    qtdep=$(otool -L "$x" | grep Qt | grep amework | grep -v ':$' | grep -v '@loader_path' | awk '{ print $1; }')
+    if [ -n "$qtdep" ]; then
+	echo
+	echo "ERROR: File $x depends on Qt framework(s) not apparently present in the bundle:"
+	echo $qtdep
+	exit 1
+    fi
 done
 
 echo "Done: be sure to run the app and see that it works!"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deploy/win32/build.bat	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,26 @@
+rem  Run this from within the top-level project dir: deploy\win32\build.bat
+rem  To build from clean, delete the folder build_win32
+
+set STARTPWD=%CD%
+
+set QTDIR=C:\Qt\5.11.2\mingw53_32
+if not exist %QTDIR% (
+@   echo Could not find 32-bit Qt
+@   exit /b 2
+)
+
+set ORIGINALPATH=%PATH%
+set PATH=%PATH%;C:\Program Files (x86)\SMLNJ\bin;%QTDIR%\bin;C:\Qt\Tools\QtCreator\bin;C:\Qt\Tools\mingw530_32\bin
+
+cd %STARTPWD%
+
+mkdir build_win32
+cd build_win32
+
+qmake -spec win32-g++ -r ..\easyhg.pro
+if %errorlevel% neq 0 exit /b %errorlevel%
+
+mingw32-make
+if %errorlevel% neq 0 exit /b %errorlevel%
+
+set PATH=%ORIGINALPATH%
--- a/deploy/win32/installjammer/EasyMercurial.mpi	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2250 +0,0 @@
-array set info {
-AllowLanguageSelection
-{Yes}
-
-AppName
-{EasyMercurial}
-
-ApplicationID
-{4ED247D2-7A93-40E4-AA7E-D7CB55AA0411}
-
-ApplicationURL
-{}
-
-AutoRefreshFiles
-{Yes}
-
-BuildFailureAction
-{Fail (recommended)}
-
-CancelledInstallAction
-{Rollback and Stop}
-
-CleanupCancelledInstall
-{Yes}
-
-CommandLineFailureAction
-{Fail (recommended)}
-
-Company
-{Queen Mary, University of London}
-
-CompressionLevel
-{6}
-
-CompressionMethod
-{zlib}
-
-Copyright
-{}
-
-CreateDesktopShortcut
-{Yes}
-
-CreateQuickLaunchShortcut
-{Yes}
-
-DefaultDirectoryLocation
-{}
-
-DefaultLanguage
-{English}
-
-DefaultToSystemLanguage
-{Yes}
-
-EnableResponseFiles
-{Yes}
-
-Ext
-{.exe}
-
-ExtractSolidArchivesOnStartup
-{No}
-
-Icon
-{Modern/Small/SetupModernSmall01.gif}
-
-IgnoreDirectories
-{}
-
-IgnoreFiles
-{}
-
-Image
-{Modern/SetupModern01.gif}
-
-IncludeDebugging
-{Yes}
-
-InstallPassword
-{}
-
-InstallVersion
-{0.3.1.0}
-
-Language,ca
-{Yes}
-
-Language,cs
-{Yes}
-
-Language,de
-{Yes}
-
-Language,en
-{Yes}
-
-Language,es
-{Yes}
-
-Language,fr
-{Yes}
-
-Language,hu
-{Yes}
-
-Language,it
-{Yes}
-
-Language,lt
-{Yes}
-
-Language,nl
-{Yes}
-
-Language,pl
-{Yes}
-
-Language,pt_br
-{Yes}
-
-Language,ru
-{Yes}
-
-LastIgnoreDirectories
-{}
-
-LastIgnoreFiles
-{}
-
-LaunchApplication
-{Yes}
-
-PackageDescription
-{}
-
-PackageLicense
-{}
-
-PackageMaintainer
-{}
-
-PackageName
-{<%ShortAppName%>}
-
-PackagePackager
-{}
-
-PackageRelease
-{<%PatchVersion%>}
-
-PackageSummary
-{}
-
-PackageVersion
-{<%MajorVersion%>.<%MinorVersion%>}
-
-PreserveFileAttributes
-{Yes}
-
-PreserveFilePermissions
-{Yes}
-
-ProjectID
-{CF8A2B09-3CCF-4788-AE7C-93F8BFB98225}
-
-ProjectVersion
-{1.2.15.2}
-
-SaveOnlyToplevelDirs
-{No}
-
-ShortAppName
-{EasyMercurial}
-
-SkipUnusedFileGroups
-{Yes}
-
-Theme
-{Modern_Wizard}
-
-ThemeDir
-{Modern_Wizard}
-
-ThemeVersion
-{1}
-
-UpgradeApplicationID
-{}
-
-Version
-{0.3.1}
-
-ViewReadme
-{Yes}
-
-WizardHeight
-{365}
-
-WizardWidth
-{500}
-
-}
-
-array set ::InstallJammer::InstallCommandLineOptions {
-debug
-{Debugging Switch Yes No {} {run installer in debug mode}}
-
-debugconsole
-{ShowConsole Switch Yes No {} {run installer with a debug console open}}
-
-mode
-{InstallMode Choice No No {Console Default Silent Standard} {set the mode to run the installer in}}
-
-prefix
-{InstallDir String No No {} {set the installation directory}}
-
-test
-{Testing Switch Yes No {} {run installer without installing any files}}
-
-}
-array set ::InstallJammer::UninstallCommandLineOptions {
-debug
-{Debugging Switch Yes No {} {run uninstaller in debug mode}}
-
-debugconsole
-{ShowConsole Switch Yes No {} {run uninstaller with a debug console open}}
-
-mode
-{UninstallMode Choice No No {Console Silent Standard} {set the mode to run the uninstaller in}}
-
-test
-{Testing Switch Yes No {} {run uninstaller without uninstalling any files}}
-
-}
-FileGroup ::7D8589FC-AF69-4DFB-9DE3-363BAFB40C0D -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-5-x86 FreeBSD-6-x86 FreeBSD-7-x86 HPUX-hppa Linux-x86 Linux-x86_64 Solaris-sparc Solaris-x86 Windows TarArchive ZipArchive} -name {Program Files} -parent FileGroups
-File ::FA3F40D7-928A-471C-B6FB-06664B4AF5DD -type dir -directory <%InstallDir%> -name D:/EasyMercurial-0.3.1-win32 -parent 7D8589FC-AF69-4DFB-9DE3-363BAFB40C0D
-File ::71B67954-4ACE-47B8-B154-709FB6B52AB5 -name easyhg-icon.ico -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::A4AFAC5C-2CDD-4C0A-A50A-DD6C6537F747 -name easyhg-icon.png -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::671FB0C6-C377-457D-804B-4091E2852874 -name EasyMercurial.exe -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::2FF4EB68-CF87-4572-86E5-3731E9668588 -name hg.exe -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::B3F4C76F-0711-4D91-BC39-3FA4EB2DCAC1 -name kdiff3.exe -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::90D1E310-E586-4E73-B811-BFB7AF5E9005 -name library.zip -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::67827AA9-3963-4F9C-87AA-F84537D75AA5 -name msvcp100.dll -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::922E8B93-2D65-4B5A-815A-6FE22C5D4AC7 -name msvcr100.dll -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::AE3198B4-4759-45E3-8004-13904F0C20AB -name python26.dll -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::D01E8609-5B54-464E-B2CE-8ACCA9189BFB -name QtCore4.dll -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::0938068D-8ED3-41EE-BFCA-EFDFA4560B52 -name QtGui4.dll -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::30DD0704-C89E-462E-971B-A808AFD34C82 -name QtNetwork4.dll -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::EFF54862-F9DC-4934-87D3-4D706C15FFF3 -name sip.pyd -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::C75BD3D6-63C0-4928-844B-269034F65F8E -type dir -name PyQt4 -parent FA3F40D7-928A-471C-B6FB-06664B4AF5DD
-File ::CE2A9028-262A-4D4A-B387-70CBA22C8A1B -name __init__.py -parent C75BD3D6-63C0-4928-844B-269034F65F8E
-File ::88560760-DA1B-47C3-AA01-53CBE6E28FF5 -name __init__.pyo -parent C75BD3D6-63C0-4928-844B-269034F65F8E
-File ::3AD4F42B-A4CD-44CF-9A14-40774B90F346 -name QtCore.pyd -parent C75BD3D6-63C0-4928-844B-269034F65F8E
-File ::9B3010D3-3AAF-400E-B8C8-3B246EC0A206 -name QtGui.pyd -parent C75BD3D6-63C0-4928-844B-269034F65F8E
-Component ::0B08768B-E5AA-4701-B9CE-424C9F6AA96B -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-5-x86 FreeBSD-6-x86 FreeBSD-7-x86 HPUX-hppa Linux-x86 Linux-x86_64 Solaris-sparc Solaris-x86 Windows} -name {Default Component} -parent Components
-SetupType ::489BB600-70D6-4EC8-B032-F5FF6FDD532F -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-5-x86 FreeBSD-6-x86 FreeBSD-7-x86 HPUX-hppa Linux-x86 Linux-x86_64 Solaris-sparc Solaris-x86 Windows} -name Typical -parent SetupTypes
-SetupType ::2A3B15B2-D3B0-43DD-9FDD-581278BD0EA6 -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-5-x86 FreeBSD-6-x86 FreeBSD-7-x86 HPUX-hppa Linux-x86 Linux-x86_64 Solaris-sparc Solaris-x86 Windows} -name Custom -parent SetupTypes
-
-InstallComponent 2AE46278-0442-4ACB-92BC-33F85D961AE6 -setup Install -type pane -title {Welcome Screen} -component Welcome -active Yes -parent StandardInstall
-InstallComponent 806BCA96-55DE-4551-BD24-7BDA967C0122 -setup Install -type pane -conditions {56A12F16-B339-4D29-9B64-FB118A810DF0 A5D3BBB1-85B1-4EB2-B853-8B55062277FC} -title {Select Destination} -component SelectDestination -active Yes -parent StandardInstall
-Condition 56A12F16-B339-4D29-9B64-FB118A810DF0 -active Yes -parent 806BCA96-55DE-4551-BD24-7BDA967C0122 -title {Script Condition} -component ScriptCondition -TreeObject::id 56A12F16-B339-4D29-9B64-FB118A810DF0
-Condition A5D3BBB1-85B1-4EB2-B853-8B55062277FC -active Yes -parent 806BCA96-55DE-4551-BD24-7BDA967C0122 -title {File Permission Condition} -component FilePermissionCondition -TreeObject::id A5D3BBB1-85B1-4EB2-B853-8B55062277FC
-InstallComponent FC22F272-B809-410E-A4C3-C3143C71FC94 -setup Install -type pane -title {Start Copying Files} -component StartCopyingFiles -active Yes -parent StandardInstall
-InstallComponent 436E86CF-1049-4020-87C6-08919DF47E9D -setup Install -type pane -title {Copying Files} -component CopyFiles -active Yes -parent StandardInstall
-InstallComponent 89C128FC-A7C5-462B-ACAD-DFAF81BE1B4B -setup Install -type action -title {Disable Buttons} -component ModifyWidget -active Yes -parent 436E86CF-1049-4020-87C6-08919DF47E9D
-InstallComponent 566F51B1-AFB2-4D36-AC8A-F8EF2E41CFCD -setup Install -type action -title {Install Everything} -component ExecuteAction -active Yes -parent 436E86CF-1049-4020-87C6-08919DF47E9D
-InstallComponent A8D0E4E2-39C9-43A4-8B40-061F397C927F -setup Install -type action -title {Move Forward} -component MoveForward -active Yes -parent 436E86CF-1049-4020-87C6-08919DF47E9D
-InstallComponent 137AE532-86CF-44B3-9167-0132755FA850 -setup Install -type pane -title {Setup Complete} -component SetupComplete -active Yes -parent StandardInstall
-InstallComponent DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF -setup Install -type action -conditions {7D1EBBDC-5691-4044-BB19-6CE7D704D434 6AED9C7F-5DEE-4356-A1E3-8D9B67EA577A} -title {View Readme Checkbutton} -component AddWidget -active Yes -parent 137AE532-86CF-44B3-9167-0132755FA850
-Condition 7D1EBBDC-5691-4044-BB19-6CE7D704D434 -active Yes -parent DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 7D1EBBDC-5691-4044-BB19-6CE7D704D434
-Condition 6AED9C7F-5DEE-4356-A1E3-8D9B67EA577A -active Yes -parent DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF -title {String Is Condition} -component StringIsCondition -TreeObject::id 6AED9C7F-5DEE-4356-A1E3-8D9B67EA577A
-InstallComponent 96B93C29-6883-4B14-A4BB-063769CC7311 -setup Install -type action -conditions {9688B263-B6EC-40CD-9B31-0D1843879A88 E92E5657-B9EE-4F5A-9250-24EE1E67F387} -title {Launch Application Checkbutton} -component AddWidget -active Yes -parent 137AE532-86CF-44B3-9167-0132755FA850
-Condition 9688B263-B6EC-40CD-9B31-0D1843879A88 -active Yes -parent 96B93C29-6883-4B14-A4BB-063769CC7311 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 9688B263-B6EC-40CD-9B31-0D1843879A88
-Condition E92E5657-B9EE-4F5A-9250-24EE1E67F387 -active Yes -parent 96B93C29-6883-4B14-A4BB-063769CC7311 -title {String Is Condition} -component StringIsCondition -TreeObject::id E92E5657-B9EE-4F5A-9250-24EE1E67F387
-InstallComponent 1ED68248-972F-4343-BEF4-C91ED3618601 -setup Install -type action -conditions {F97966F6-B23B-4C59-95BC-A0A06B71B2BF 1BF9B52D-D364-470B-8D12-A5ADD1A47B5B} -title {Desktop Shortcut Checkbutton} -component AddWidget -active Yes -parent 137AE532-86CF-44B3-9167-0132755FA850
-Condition F97966F6-B23B-4C59-95BC-A0A06B71B2BF -active Yes -parent 1ED68248-972F-4343-BEF4-C91ED3618601 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id F97966F6-B23B-4C59-95BC-A0A06B71B2BF
-Condition 1BF9B52D-D364-470B-8D12-A5ADD1A47B5B -active Yes -parent 1ED68248-972F-4343-BEF4-C91ED3618601 -title {String Is Condition} -component StringIsCondition -TreeObject::id 1BF9B52D-D364-470B-8D12-A5ADD1A47B5B
-InstallComponent 7B9998FD-0077-4EC1-8F96-88ABBE05A702 -setup Install -type action -conditions {147E1FCB-3B06-4776-A3CE-7FE6B93DCC84 5E8028FA-E17F-4AD8-BAC5-4F813927D713 1D290830-4FD2-4440-A00E-2A8668632CE1} -title {Quick Launch Shortcut Checkbutton} -component AddWidget -active Yes -parent 137AE532-86CF-44B3-9167-0132755FA850
-Condition 147E1FCB-3B06-4776-A3CE-7FE6B93DCC84 -active Yes -parent 7B9998FD-0077-4EC1-8F96-88ABBE05A702 -title {Platform Condition} -component PlatformCondition -TreeObject::id 147E1FCB-3B06-4776-A3CE-7FE6B93DCC84
-Condition 5E8028FA-E17F-4AD8-BAC5-4F813927D713 -active Yes -parent 7B9998FD-0077-4EC1-8F96-88ABBE05A702 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 5E8028FA-E17F-4AD8-BAC5-4F813927D713
-Condition 1D290830-4FD2-4440-A00E-2A8668632CE1 -active Yes -parent 7B9998FD-0077-4EC1-8F96-88ABBE05A702 -title {String Is Condition} -component StringIsCondition -TreeObject::id 1D290830-4FD2-4440-A00E-2A8668632CE1
-InstallComponent 312F5FB0-FF24-477F-8A66-F333A5124D9D -setup Install -type pane -title {Copying Files} -component CopyFiles -active Yes -parent DefaultInstall
-InstallComponent 5E4716C8-584A-4432-A4A0-F4E52EE4C0F7 -setup Install -type action -title {Disable Buttons} -component ModifyWidget -active Yes -parent 312F5FB0-FF24-477F-8A66-F333A5124D9D
-InstallComponent 697B5256-7EBA-4599-B533-6B1A540B5961 -setup Install -type action -title {Install Everything} -component ExecuteAction -active Yes -parent 312F5FB0-FF24-477F-8A66-F333A5124D9D
-InstallComponent B90CCD39-E5F7-4084-8C0E-F8008945BB92 -setup Install -type action -title {Move Forward} -component MoveForward -active Yes -parent 312F5FB0-FF24-477F-8A66-F333A5124D9D
-InstallComponent A0694726-591A-4EBA-AAD2-075E5F9091EE -setup Install -type pane -title {Setup Complete} -component SetupComplete -active Yes -parent DefaultInstall
-InstallComponent 18C3EF37-5C55-43E6-A223-7A4444357E2F -setup Install -type action -conditions {26B9948E-44F1-40C7-8CBA-419E51CF5575 3D587A2C-2764-4105-9E8B-46443BFF7FE3} -title {View Readme Checkbutton} -component AddWidget -active Yes -parent A0694726-591A-4EBA-AAD2-075E5F9091EE
-Condition 26B9948E-44F1-40C7-8CBA-419E51CF5575 -active Yes -parent 18C3EF37-5C55-43E6-A223-7A4444357E2F -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 26B9948E-44F1-40C7-8CBA-419E51CF5575
-Condition 3D587A2C-2764-4105-9E8B-46443BFF7FE3 -active Yes -parent 18C3EF37-5C55-43E6-A223-7A4444357E2F -title {String Is Condition} -component StringIsCondition -TreeObject::id 3D587A2C-2764-4105-9E8B-46443BFF7FE3
-InstallComponent DA64DBB1-48C9-481E-A9B5-79114AE13ED3 -setup Install -type action -conditions {78E848D0-C357-4F8D-9A49-2F1C075E8C87 46287AD0-DC63-4C65-A9D9-CF6D2DDC28BF} -title {Launch Application Checkbutton} -component AddWidget -active Yes -parent A0694726-591A-4EBA-AAD2-075E5F9091EE
-Condition 78E848D0-C357-4F8D-9A49-2F1C075E8C87 -active Yes -parent DA64DBB1-48C9-481E-A9B5-79114AE13ED3 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 78E848D0-C357-4F8D-9A49-2F1C075E8C87
-Condition 46287AD0-DC63-4C65-A9D9-CF6D2DDC28BF -active Yes -parent DA64DBB1-48C9-481E-A9B5-79114AE13ED3 -title {String Is Condition} -component StringIsCondition -TreeObject::id 46287AD0-DC63-4C65-A9D9-CF6D2DDC28BF
-InstallComponent 5F872846-216A-4EAE-B600-15CBEE8DF4CF -setup Install -type action -conditions {8DA0CB01-C65E-4F61-842C-38C1AD3087C1 863CE846-BD62-448A-BEC7-DAFF4E9C61A4} -title {Desktop Shortcut Checkbutton} -component AddWidget -active Yes -parent A0694726-591A-4EBA-AAD2-075E5F9091EE
-Condition 8DA0CB01-C65E-4F61-842C-38C1AD3087C1 -active Yes -parent 5F872846-216A-4EAE-B600-15CBEE8DF4CF -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 8DA0CB01-C65E-4F61-842C-38C1AD3087C1
-Condition 863CE846-BD62-448A-BEC7-DAFF4E9C61A4 -active Yes -parent 5F872846-216A-4EAE-B600-15CBEE8DF4CF -title {String Is Condition} -component StringIsCondition -TreeObject::id 863CE846-BD62-448A-BEC7-DAFF4E9C61A4
-InstallComponent BB52149D-79DD-4120-8BB8-FF9E81E79E42 -setup Install -type action -conditions {822B14AA-B0F0-4CFD-9E69-C5ACA1946599 040D4822-74FC-432F-A45B-36BA44FEFF99 E4CE56DE-497F-4D32-A6AC-2D03EBF05FD6} -title {Quick Launch Shortcut Checkbutton} -component AddWidget -active Yes -parent A0694726-591A-4EBA-AAD2-075E5F9091EE
-Condition 822B14AA-B0F0-4CFD-9E69-C5ACA1946599 -active Yes -parent BB52149D-79DD-4120-8BB8-FF9E81E79E42 -title {Platform Condition} -component PlatformCondition -TreeObject::id 822B14AA-B0F0-4CFD-9E69-C5ACA1946599
-Condition 040D4822-74FC-432F-A45B-36BA44FEFF99 -active Yes -parent BB52149D-79DD-4120-8BB8-FF9E81E79E42 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 040D4822-74FC-432F-A45B-36BA44FEFF99
-Condition E4CE56DE-497F-4D32-A6AC-2D03EBF05FD6 -active Yes -parent BB52149D-79DD-4120-8BB8-FF9E81E79E42 -title {String Is Condition} -component StringIsCondition -TreeObject::id E4CE56DE-497F-4D32-A6AC-2D03EBF05FD6
-InstallComponent 8908F64E-3013-478A-8CB2-A232E4E56511 -setup Install -type action -title {Prompt to continue installation} -component ConsoleAskYesOrNo -active Yes -parent ConsoleInstall
-InstallComponent 0F625BC9-C196-49AF-A1D1-B365516BB1D0 -setup Install -type action -conditions 72AECA62-50D3-4BBE-87B8-9B7528CCC7BD -title {Exit if they said no} -component Exit -active Yes -parent ConsoleInstall
-Condition 72AECA62-50D3-4BBE-87B8-9B7528CCC7BD -active Yes -parent 0F625BC9-C196-49AF-A1D1-B365516BB1D0 -title {String Is Condition} -component StringIsCondition -TreeObject::id 72AECA62-50D3-4BBE-87B8-9B7528CCC7BD
-InstallComponent B5EB2E45-BECA-421A-A2F1-1A1D03FBA8E5 -setup Install -type action -conditions D66EB685-05F6-4789-95CD-B3BF714CCFF6 -title {Prompt for install destination} -component ConsoleGetUserInput -active Yes -parent ConsoleInstall
-Condition D66EB685-05F6-4789-95CD-B3BF714CCFF6 -active Yes -parent B5EB2E45-BECA-421A-A2F1-1A1D03FBA8E5 -title {File Permission Condition} -component FilePermissionCondition -TreeObject::id D66EB685-05F6-4789-95CD-B3BF714CCFF6
-InstallComponent EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1 -setup Install -type action -title {Output Installing Message} -component ConsoleMessage -active Yes -parent ConsoleInstall
-InstallComponent 53358455-6066-4EC4-B313-32B960050C2B -setup Install -type action -title {Install Everything} -component ExecuteAction -active Yes -parent ConsoleInstall
-InstallComponent 1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2 -setup Install -type action -title {Output Install Complete Message} -component ConsoleMessage -active Yes -parent ConsoleInstall
-InstallComponent 6906B67A-2CE8-4625-90CF-1DFF4F5D5111 -setup Install -type action -title Exit -component Exit -active Yes -parent ConsoleInstall
-InstallComponent CF5E291F-7B8C-4EA0-8A9B-1F7FAE3F58BD -setup Install -type action -title {Install Everything} -component ExecuteAction -active Yes -parent SilentInstall
-InstallComponent F212318C-2AAC-4B73-8A27-791B38F9D509 -setup Install -type action -title Exit -component Exit -active Yes -parent SilentInstall
-InstallComponent 0ADB4A61-3828-4459-B2F2-0EBD8440A5EE -setup Install -type actiongroup -title {Setup Actions} -alias {Setup Actions} -active Yes -parent ActionGroupsInstall
-InstallComponent 4B9F4FD3-2A7D-47E5-8EDD-A789EE7BEB92 -setup Install -type actiongroup -title {Startup Actions} -alias {Startup Actions} -active Yes -parent ActionGroupsInstall
-InstallComponent 39D5E2BB-5E09-40E5-B17F-9A087BC40984 -setup Install -type action -conditions {DBF8F9B2-6265-4155-9984-8F6EEE64E634 7BF3B571-3EE3-485C-BFB9-3732B067B63F} -title Exit -component Exit -active Yes -parent 4B9F4FD3-2A7D-47E5-8EDD-A789EE7BEB92
-Condition DBF8F9B2-6265-4155-9984-8F6EEE64E634 -active Yes -parent 39D5E2BB-5E09-40E5-B17F-9A087BC40984 -title {String Is Condition} -component StringIsCondition -TreeObject::id DBF8F9B2-6265-4155-9984-8F6EEE64E634
-Condition 7BF3B571-3EE3-485C-BFB9-3732B067B63F -active Yes -parent 39D5E2BB-5E09-40E5-B17F-9A087BC40984 -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id 7BF3B571-3EE3-485C-BFB9-3732B067B63F
-InstallComponent 3DA13144-7358-45B9-963D-D4C46070235F -setup Install -type action -title {Create Install Panes} -component CreateInstallPanes -active Yes -parent 4B9F4FD3-2A7D-47E5-8EDD-A789EE7BEB92
-InstallComponent 96D47F15-DF92-4ED1-B3B7-568EBD24CB9B -setup Install -type actiongroup -title {Install Actions} -alias {Install Actions} -active Yes -parent ActionGroupsInstall
-InstallComponent A02B0D14-89F9-4D6E-A640-D32F8DE84CFE -setup Install -type action -title {Install Selected Files} -component InstallSelectedFiles -active Yes -parent 96D47F15-DF92-4ED1-B3B7-568EBD24CB9B
-InstallComponent 3055A4EC-94FA-407B-B69A-9670D30B58AE -setup Install -type action -conditions 7571DE26-7BDF-456E-86DE-FE9362FFB11C -title {Install Uninstaller} -component InstallUninstaller -active Yes -parent 96D47F15-DF92-4ED1-B3B7-568EBD24CB9B
-Condition 7571DE26-7BDF-456E-86DE-FE9362FFB11C -active Yes -parent 3055A4EC-94FA-407B-B69A-9670D30B58AE -title {String Is Condition} -component StringIsCondition -TreeObject::id 7571DE26-7BDF-456E-86DE-FE9362FFB11C
-InstallComponent 08BDABA6-F507-4500-8F7B-26A8A58CD52E -setup Install -type action -conditions 67145EC1-13C4-4E74-B5DE-A78E1B266785 -title {Windows Uninstall Registry} -component AddWindowsUninstallEntry -active Yes -parent 96D47F15-DF92-4ED1-B3B7-568EBD24CB9B
-Condition 67145EC1-13C4-4E74-B5DE-A78E1B266785 -active Yes -parent 08BDABA6-F507-4500-8F7B-26A8A58CD52E -title {String Is Condition} -component StringIsCondition -TreeObject::id 67145EC1-13C4-4E74-B5DE-A78E1B266785
-InstallComponent 8B1A41D1-6E41-41B4-80F6-15493100F9FC -setup Install -type action -conditions C5CB03C7-3D7A-4DC5-B335-FE84A9A1D18A -title {Program Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 96D47F15-DF92-4ED1-B3B7-568EBD24CB9B
-Condition C5CB03C7-3D7A-4DC5-B335-FE84A9A1D18A -active Yes -parent 8B1A41D1-6E41-41B4-80F6-15493100F9FC -title {String Is Condition} -component StringIsCondition -TreeObject::id C5CB03C7-3D7A-4DC5-B335-FE84A9A1D18A
-InstallComponent 7C59ECDE-E0B1-4B3B-9ADE-364347F153E1 -setup Install -type action -conditions 1AB72C39-609E-4F09-8834-3D3C8A68BEB1 -title {Uninstall Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 96D47F15-DF92-4ED1-B3B7-568EBD24CB9B
-Condition 1AB72C39-609E-4F09-8834-3D3C8A68BEB1 -active Yes -parent 7C59ECDE-E0B1-4B3B-9ADE-364347F153E1 -title {String Is Condition} -component StringIsCondition -TreeObject::id 1AB72C39-609E-4F09-8834-3D3C8A68BEB1
-InstallComponent D8F58F63-F6A3-4FF8-8579-5F58FD242EAC -setup Install -type actiongroup -title {Finish Actions} -alias {Finish Actions} -active Yes -parent ActionGroupsInstall
-InstallComponent F243AA46-CB2B-46BA-8254-0CE5192BFD7B -setup Install -type action -conditions {1AA9090F-36B7-4A63-8650-DD76DA7B787A D361C7FD-0376-4C29-8E8F-4056D0F505B6} -title {Install Desktop Shortcut} -component InstallDesktopShortcut -active Yes -parent D8F58F63-F6A3-4FF8-8579-5F58FD242EAC
-Condition 1AA9090F-36B7-4A63-8650-DD76DA7B787A -active Yes -parent F243AA46-CB2B-46BA-8254-0CE5192BFD7B -title {String Is Condition} -component StringIsCondition -TreeObject::id 1AA9090F-36B7-4A63-8650-DD76DA7B787A
-Condition D361C7FD-0376-4C29-8E8F-4056D0F505B6 -active Yes -parent F243AA46-CB2B-46BA-8254-0CE5192BFD7B -title {File Exists Condition} -component FileExistsCondition -TreeObject::id D361C7FD-0376-4C29-8E8F-4056D0F505B6
-InstallComponent AF5F4D19-561B-4E81-8A7A-D0C9DCEE7487 -setup Install -type action -conditions {F114794C-193C-4E5D-8830-4BBF8FDF9FC8 C1C77698-90D3-49DA-8751-B927F3649C44} -title {Install Quick Launch Shortcut} -component InstallWindowsShortcut -active Yes -parent D8F58F63-F6A3-4FF8-8579-5F58FD242EAC
-Condition F114794C-193C-4E5D-8830-4BBF8FDF9FC8 -active Yes -parent AF5F4D19-561B-4E81-8A7A-D0C9DCEE7487 -title {String Is Condition} -component StringIsCondition -TreeObject::id F114794C-193C-4E5D-8830-4BBF8FDF9FC8
-Condition C1C77698-90D3-49DA-8751-B927F3649C44 -active Yes -parent AF5F4D19-561B-4E81-8A7A-D0C9DCEE7487 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id C1C77698-90D3-49DA-8751-B927F3649C44
-InstallComponent 4629322A-B41A-41FD-976D-987B375AFECC -setup Install -type action -conditions {54865810-0EB1-44F6-A737-11953CDA59A0 6967FABA-B1BD-4160-845A-D3582B8967A9 1B65C97D-8937-4145-9BA5-EAD9C908BDBE} -title {View Readme Window} -component TextWindow -active Yes -parent D8F58F63-F6A3-4FF8-8579-5F58FD242EAC
-Condition 54865810-0EB1-44F6-A737-11953CDA59A0 -active Yes -parent 4629322A-B41A-41FD-976D-987B375AFECC -title {String Is Condition} -component StringIsCondition -TreeObject::id 54865810-0EB1-44F6-A737-11953CDA59A0
-Condition 6967FABA-B1BD-4160-845A-D3582B8967A9 -active Yes -parent 4629322A-B41A-41FD-976D-987B375AFECC -title {String Is Condition} -component StringIsCondition -TreeObject::id 6967FABA-B1BD-4160-845A-D3582B8967A9
-Condition 1B65C97D-8937-4145-9BA5-EAD9C908BDBE -active Yes -parent 4629322A-B41A-41FD-976D-987B375AFECC -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 1B65C97D-8937-4145-9BA5-EAD9C908BDBE
-InstallComponent AD493088-BC25-4A05-83FF-CAED2A7960F0 -setup Install -type action -conditions {78B27C47-637A-42F5-934A-4EFA6164572F 16ED1571-4A24-4E75-8D65-8F2EF69903AB D7F35084-4D8C-4118-B96C-48FF9496BFE2} -title {Launch Application} -component ExecuteExternalProgram -active Yes -parent D8F58F63-F6A3-4FF8-8579-5F58FD242EAC
-Condition 78B27C47-637A-42F5-934A-4EFA6164572F -active Yes -parent AD493088-BC25-4A05-83FF-CAED2A7960F0 -title {String Is Condition} -component StringIsCondition -TreeObject::id 78B27C47-637A-42F5-934A-4EFA6164572F
-Condition 16ED1571-4A24-4E75-8D65-8F2EF69903AB -active Yes -parent AD493088-BC25-4A05-83FF-CAED2A7960F0 -title {String Is Condition} -component StringIsCondition -TreeObject::id 16ED1571-4A24-4E75-8D65-8F2EF69903AB
-Condition D7F35084-4D8C-4118-B96C-48FF9496BFE2 -active Yes -parent AD493088-BC25-4A05-83FF-CAED2A7960F0 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id D7F35084-4D8C-4118-B96C-48FF9496BFE2
-InstallComponent 0CEB080C-8AD4-4C6C-BA7B-92A10D26D88C -setup Install -type actiongroup -title {Cancel Actions} -alias {Cancel Actions} -active Yes -parent ActionGroupsInstall
-InstallComponent 44DEB994-3227-4D45-8AAF-97C6BF7DA66E -setup Uninstall -type pane -title Uninstall -component Uninstall -active Yes -parent StandardUninstall
-InstallComponent 392B6093-41C9-4CAA-BE29-4F2E2055363D -setup Uninstall -type action -title {Modify Widget} -component ModifyWidget -active Yes -parent 44DEB994-3227-4D45-8AAF-97C6BF7DA66E
-InstallComponent F1F054C5-13AA-41B3-8BF3-79A7B63EBFC9 -setup Uninstall -type action -title {Execute Action} -component ExecuteAction -active Yes -parent 44DEB994-3227-4D45-8AAF-97C6BF7DA66E
-InstallComponent F5B7F0C0-AB9C-4940-A271-6E51DB56E284 -setup Uninstall -type action -title {Move Forward} -component MoveForward -active Yes -parent 44DEB994-3227-4D45-8AAF-97C6BF7DA66E
-InstallComponent 14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72 -setup Uninstall -type pane -conditions E26CCBFE-A672-4B88-BD04-9A11859EDD42 -title {Uninstall Details} -component UninstallDetails -active Yes -parent StandardUninstall
-Condition E26CCBFE-A672-4B88-BD04-9A11859EDD42 -active Yes -parent 14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72 -title {String Is Condition} -component StringIsCondition -TreeObject::id E26CCBFE-A672-4B88-BD04-9A11859EDD42
-InstallComponent B41385EA-BF12-40E2-8C63-8D34C9085CB8 -setup Uninstall -type pane -title {Uninstall Complete} -component UninstallComplete -active Yes -parent StandardUninstall
-InstallComponent 7377E888-711F-4C79-B921-2A1A3F1F0CA5 -setup Uninstall -type action -title {Console Ask Yes Or No} -component ConsoleAskYesOrNo -active Yes -parent ConsoleUninstall
-InstallComponent 3E46EE98-D3C8-4984-A581-DAD954625F22 -setup Uninstall -type action -conditions 12E3C8CE-B316-4D20-B2E9-4325A24D4549 -title Exit -component Exit -active Yes -parent ConsoleUninstall
-Condition 12E3C8CE-B316-4D20-B2E9-4325A24D4549 -active Yes -parent 3E46EE98-D3C8-4984-A581-DAD954625F22 -title {String Is Condition} -component StringIsCondition -TreeObject::id 12E3C8CE-B316-4D20-B2E9-4325A24D4549
-InstallComponent 09720EAD-E327-41AC-B5C4-CAB372D427D8 -setup Uninstall -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleUninstall
-InstallComponent C1DA1E92-56F3-4EFE-930E-852FEEF558BF -setup Uninstall -type action -title {Execute Action} -component ExecuteAction -active Yes -parent ConsoleUninstall
-InstallComponent 446444AE-E509-41B3-92E1-6B6EFFA1FD12 -setup Uninstall -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleUninstall
-InstallComponent 0705315B-1FAD-4054-9223-2741A772D570 -setup Uninstall -type action -title Exit -component Exit -active Yes -parent ConsoleUninstall
-InstallComponent BB024539-5037-4A7B-95BE-26C233CD3842 -setup Uninstall -type action -title {Uninstall Everything} -component ExecuteAction -active Yes -parent SilentUninstall
-InstallComponent 7729A27E-04C9-4ED3-9F8E-5971CC308F51 -setup Uninstall -type action -title Exit -component Exit -active Yes -parent SilentUninstall
-InstallComponent 50259665-CDDA-46AC-ADFD-D493028BB700 -setup Uninstall -type actiongroup -title {Setup Actions} -alias {Setup Actions} -active Yes -parent ActionGroupsUninstall
-InstallComponent 23C9F3A6-8FCB-4B22-A553-CCF3300A1097 -setup Uninstall -type actiongroup -title {Startup Actions} -alias {Startup Actions} -active Yes -parent ActionGroupsUninstall
-InstallComponent 08D33FE8-3A67-4F78-B79F-747F0497187B -setup Uninstall -type action -conditions {69663474-61D6-4E8B-B4B8-288E40DEFADD 2013F392-6A72-4E50-8294-F83C8DBECFEA} -title Exit -component Exit -active Yes -parent 23C9F3A6-8FCB-4B22-A553-CCF3300A1097
-Condition 69663474-61D6-4E8B-B4B8-288E40DEFADD -active Yes -parent 08D33FE8-3A67-4F78-B79F-747F0497187B -title {String Is Condition} -component StringIsCondition -TreeObject::id 69663474-61D6-4E8B-B4B8-288E40DEFADD
-Condition 2013F392-6A72-4E50-8294-F83C8DBECFEA -active Yes -parent 08D33FE8-3A67-4F78-B79F-747F0497187B -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id 2013F392-6A72-4E50-8294-F83C8DBECFEA
-InstallComponent BDBE9DC0-4E01-4B21-8ED7-680161E92902 -setup Uninstall -type actiongroup -title {Uninstall Actions} -alias {Uninstall Actions} -active Yes -parent ActionGroupsUninstall
-InstallComponent EF10895C-0CC7-4072-AFB5-B8D5ADD27E1B -setup Uninstall -type action -title {Uninstall Selected Files} -component UninstallSelectedFiles -active Yes -parent BDBE9DC0-4E01-4B21-8ED7-680161E92902
-InstallComponent 654CF276-B044-4793-95C6-57A33433250F -setup Uninstall -type actiongroup -title {Finish Actions} -alias {Finish Actions} -active Yes -parent ActionGroupsUninstall
-InstallComponent AC727B92-7C03-4C07-9E07-CADE9DF02694 -setup Uninstall -type actiongroup -title {Cancel Actions} -alias {Cancel Actions} -active Yes -parent ActionGroupsUninstall
-
-array set Properties {
-040D4822-74FC-432F-A45B-36BA44FEFF99,CheckCondition
-{Before Action is Executed}
-
-040D4822-74FC-432F-A45B-36BA44FEFF99,Filename
-{<%ProgramExecutable%>}
-
-0705315B-1FAD-4054-9223-2741A772D570,ExitType
-{Finish}
-
-08D33FE8-3A67-4F78-B79F-747F0497187B,Comment
-{Ask the user if they want to proceed with the uninstall.}
-
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message,subst
-{1}
-
-0ADB4A61-3828-4459-B2F2-0EBD8440A5EE,Alias
-{Setup Actions}
-
-0B08768B-E5AA-4701-B9CE-424C9F6AA96B,Description,subst
-{1}
-
-0B08768B-E5AA-4701-B9CE-424C9F6AA96B,DisplayName,subst
-{1}
-
-0B08768B-E5AA-4701-B9CE-424C9F6AA96B,FileGroups
-{7D8589FC-AF69-4DFB-9DE3-363BAFB40C0D}
-
-0B08768B-E5AA-4701-B9CE-424C9F6AA96B,Name
-{Default Component}
-
-0B08768B-E5AA-4701-B9CE-424C9F6AA96B,RequiredComponent
-{Yes}
-
-0CEB080C-8AD4-4C6C-BA7B-92A10D26D88C,Alias
-{Cancel Actions}
-
-12E3C8CE-B316-4D20-B2E9-4325A24D4549,CheckCondition
-{Before Action is Executed}
-
-12E3C8CE-B316-4D20-B2E9-4325A24D4549,Operator
-{false}
-
-12E3C8CE-B316-4D20-B2E9-4325A24D4549,String
-{<%Answer%>}
-
-137AE532-86CF-44B3-9167-0132755FA850,BackButton,subst
-{1}
-
-137AE532-86CF-44B3-9167-0132755FA850,CancelButton,subst
-{1}
-
-137AE532-86CF-44B3-9167-0132755FA850,Caption,subst
-{1}
-
-137AE532-86CF-44B3-9167-0132755FA850,Message,subst
-{1}
-
-137AE532-86CF-44B3-9167-0132755FA850,NextButton,subst
-{1}
-
-147E1FCB-3B06-4776-A3CE-7FE6B93DCC84,CheckCondition
-{Before Action is Executed}
-
-147E1FCB-3B06-4776-A3CE-7FE6B93DCC84,Platform
-{Windows}
-
-14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72,BackButton,subst
-{1}
-
-14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72,CancelButton,subst
-{1}
-
-14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72,Caption,subst
-{1}
-
-14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72,Message,subst
-{1}
-
-14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72,NextButton,subst
-{1}
-
-14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72,Subtitle,subst
-{1}
-
-14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72,Text,subst
-{1}
-
-14B01D4E-2240-4EE8-8E2F-6A8FA09DBB72,Title,subst
-{1}
-
-16ED1571-4A24-4E75-8D65-8F2EF69903AB,CheckCondition
-{Before Action is Executed}
-
-16ED1571-4A24-4E75-8D65-8F2EF69903AB,String
-{<%LaunchApplication%>}
-
-18C3EF37-5C55-43E6-A223-7A4444357E2F,Background
-{#FFFFFF}
-
-18C3EF37-5C55-43E6-A223-7A4444357E2F,Text,subst
-{1}
-
-18C3EF37-5C55-43E6-A223-7A4444357E2F,Type
-{checkbutton}
-
-18C3EF37-5C55-43E6-A223-7A4444357E2F,VirtualText
-{ViewReadme}
-
-18C3EF37-5C55-43E6-A223-7A4444357E2F,X
-{185}
-
-18C3EF37-5C55-43E6-A223-7A4444357E2F,Y
-{140}
-
-1AA9090F-36B7-4A63-8650-DD76DA7B787A,CheckCondition
-{Before Action is Executed}
-
-1AA9090F-36B7-4A63-8650-DD76DA7B787A,String
-{<%CreateDesktopShortcut%>}
-
-1AB72C39-609E-4F09-8834-3D3C8A68BEB1,CheckCondition
-{Before Action is Executed}
-
-1AB72C39-609E-4F09-8834-3D3C8A68BEB1,Operator
-{false}
-
-1AB72C39-609E-4F09-8834-3D3C8A68BEB1,String
-{<%UpgradeInstall%>}
-
-1B65C97D-8937-4145-9BA5-EAD9C908BDBE,CheckCondition
-{Before Action is Executed}
-
-1B65C97D-8937-4145-9BA5-EAD9C908BDBE,Filename
-{<%ProgramReadme%>}
-
-1BF9B52D-D364-470B-8D12-A5ADD1A47B5B,CheckCondition
-{Before Action is Executed}
-
-1BF9B52D-D364-470B-8D12-A5ADD1A47B5B,Operator
-{false}
-
-1BF9B52D-D364-470B-8D12-A5ADD1A47B5B,String
-{<%InstallStopped%>}
-
-1D290830-4FD2-4440-A00E-2A8668632CE1,CheckCondition
-{Before Action is Executed}
-
-1D290830-4FD2-4440-A00E-2A8668632CE1,Operator
-{false}
-
-1D290830-4FD2-4440-A00E-2A8668632CE1,String
-{<%InstallStopped%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message,subst
-{1}
-
-1ED68248-972F-4343-BEF4-C91ED3618601,Background
-{#FFFFFF}
-
-1ED68248-972F-4343-BEF4-C91ED3618601,Text,subst
-{1}
-
-1ED68248-972F-4343-BEF4-C91ED3618601,Type
-{checkbutton}
-
-1ED68248-972F-4343-BEF4-C91ED3618601,VirtualText
-{CreateDesktopShortcut}
-
-1ED68248-972F-4343-BEF4-C91ED3618601,X
-{185}
-
-1ED68248-972F-4343-BEF4-C91ED3618601,Y
-{180}
-
-2013F392-6A72-4E50-8294-F83C8DBECFEA,CheckCondition
-{Before Action is Executed}
-
-2013F392-6A72-4E50-8294-F83C8DBECFEA,Message,subst
-{1}
-
-2013F392-6A72-4E50-8294-F83C8DBECFEA,Title,subst
-{1}
-
-2013F392-6A72-4E50-8294-F83C8DBECFEA,TrueValue
-{No}
-
-23C9F3A6-8FCB-4B22-A553-CCF3300A1097,Alias
-{Startup Actions}
-
-26B9948E-44F1-40C7-8CBA-419E51CF5575,CheckCondition
-{Before Action is Executed}
-
-26B9948E-44F1-40C7-8CBA-419E51CF5575,Filename
-{<%ProgramReadme%>}
-
-2A3B15B2-D3B0-43DD-9FDD-581278BD0EA6,Components
-{0B08768B-E5AA-4701-B9CE-424C9F6AA96B}
-
-2A3B15B2-D3B0-43DD-9FDD-581278BD0EA6,Description,subst
-{1}
-
-2A3B15B2-D3B0-43DD-9FDD-581278BD0EA6,DisplayName,subst
-{1}
-
-2A3B15B2-D3B0-43DD-9FDD-581278BD0EA6,Name
-{Custom}
-
-2AE46278-0442-4ACB-92BC-33F85D961AE6,BackButton,subst
-{1}
-
-2AE46278-0442-4ACB-92BC-33F85D961AE6,CancelButton,subst
-{1}
-
-2AE46278-0442-4ACB-92BC-33F85D961AE6,Caption,subst
-{1}
-
-2AE46278-0442-4ACB-92BC-33F85D961AE6,Message,subst
-{1}
-
-2AE46278-0442-4ACB-92BC-33F85D961AE6,NextButton,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,BackButton,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,CancelButton,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,Caption,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,FileLabel,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,Message,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,NextButton,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,ProgressValue,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,Subtitle,subst
-{1}
-
-312F5FB0-FF24-477F-8A66-F333A5124D9D,Title,subst
-{1}
-
-392B6093-41C9-4CAA-BE29-4F2E2055363D,ExecuteAction
-{Before Pane is Displayed}
-
-392B6093-41C9-4CAA-BE29-4F2E2055363D,State
-{disabled}
-
-392B6093-41C9-4CAA-BE29-4F2E2055363D,Widget
-{NextButton; CancelButton}
-
-39D5E2BB-5E09-40E5-B17F-9A087BC40984,Comment
-{Ask the user if they want to proceed with the install.}
-
-3D587A2C-2764-4105-9E8B-46443BFF7FE3,CheckCondition
-{Before Action is Executed}
-
-3D587A2C-2764-4105-9E8B-46443BFF7FE3,Operator
-{false}
-
-3D587A2C-2764-4105-9E8B-46443BFF7FE3,String
-{<%InstallStopped%>}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,BackButton,subst
-{1}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,CancelButton,subst
-{1}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,Caption,subst
-{1}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,FileLabel,subst
-{1}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,Message,subst
-{1}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,NextButton,subst
-{1}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,ProgressValue,subst
-{1}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,Subtitle,subst
-{1}
-
-436E86CF-1049-4020-87C6-08919DF47E9D,Title,subst
-{1}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,BackButton,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,CancelButton,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,Caption,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,FileValue,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,Message,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,NextButton,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,ProgressValue,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,Subtitle,subst
-{1}
-
-44DEB994-3227-4D45-8AAF-97C6BF7DA66E,Title,subst
-{1}
-
-46287AD0-DC63-4C65-A9D9-CF6D2DDC28BF,CheckCondition
-{Before Action is Executed}
-
-46287AD0-DC63-4C65-A9D9-CF6D2DDC28BF,Operator
-{false}
-
-46287AD0-DC63-4C65-A9D9-CF6D2DDC28BF,String
-{<%InstallStopped%>}
-
-4629322A-B41A-41FD-976D-987B375AFECC,Caption,subst
-{1}
-
-4629322A-B41A-41FD-976D-987B375AFECC,CloseButton,subst
-{1}
-
-4629322A-B41A-41FD-976D-987B375AFECC,Message,subst
-{1}
-
-4629322A-B41A-41FD-976D-987B375AFECC,TextFile
-{<%ProgramReadme%>}
-
-4629322A-B41A-41FD-976D-987B375AFECC,Title,subst
-{1}
-
-489BB600-70D6-4EC8-B032-F5FF6FDD532F,Components
-{0B08768B-E5AA-4701-B9CE-424C9F6AA96B}
-
-489BB600-70D6-4EC8-B032-F5FF6FDD532F,Description,subst
-{1}
-
-489BB600-70D6-4EC8-B032-F5FF6FDD532F,DisplayName,subst
-{1}
-
-489BB600-70D6-4EC8-B032-F5FF6FDD532F,Name
-{Typical}
-
-4B9F4FD3-2A7D-47E5-8EDD-A789EE7BEB92,Alias
-{Startup Actions}
-
-50259665-CDDA-46AC-ADFD-D493028BB700,Alias
-{Setup Actions}
-
-53358455-6066-4EC4-B313-32B960050C2B,Action
-{Install Actions}
-
-54865810-0EB1-44F6-A737-11953CDA59A0,CheckCondition
-{Before Action is Executed}
-
-54865810-0EB1-44F6-A737-11953CDA59A0,String
-{<%GuiMode%>}
-
-566F51B1-AFB2-4D36-AC8A-F8EF2E41CFCD,Action
-{Install Actions}
-
-56A12F16-B339-4D29-9B64-FB118A810DF0,Comment
-{Do not display this pane if this is an upgrade install unless there is more than one installation present}
-
-56A12F16-B339-4D29-9B64-FB118A810DF0,Script
-{!<%UpgradeInstall%> || <%PreviousInstallCount%> != 1}
-
-5E4716C8-584A-4432-A4A0-F4E52EE4C0F7,ExecuteAction
-{Before Pane is Displayed}
-
-5E4716C8-584A-4432-A4A0-F4E52EE4C0F7,State
-{disabled}
-
-5E4716C8-584A-4432-A4A0-F4E52EE4C0F7,Widget
-{Back Button;Next Button}
-
-5E8028FA-E17F-4AD8-BAC5-4F813927D713,CheckCondition
-{Before Action is Executed}
-
-5E8028FA-E17F-4AD8-BAC5-4F813927D713,Filename
-{<%ProgramExecutable%>}
-
-5F872846-216A-4EAE-B600-15CBEE8DF4CF,Background
-{#FFFFFF}
-
-5F872846-216A-4EAE-B600-15CBEE8DF4CF,Text,subst
-{1}
-
-5F872846-216A-4EAE-B600-15CBEE8DF4CF,Type
-{checkbutton}
-
-5F872846-216A-4EAE-B600-15CBEE8DF4CF,VirtualText
-{CreateDesktopShortcut}
-
-5F872846-216A-4EAE-B600-15CBEE8DF4CF,X
-{185}
-
-5F872846-216A-4EAE-B600-15CBEE8DF4CF,Y
-{180}
-
-654CF276-B044-4793-95C6-57A33433250F,Alias
-{Finish Actions}
-
-67145EC1-13C4-4E74-B5DE-A78E1B266785,CheckCondition
-{Before Action is Executed}
-
-67145EC1-13C4-4E74-B5DE-A78E1B266785,Operator
-{false}
-
-67145EC1-13C4-4E74-B5DE-A78E1B266785,String
-{<%UpgradeInstall%>}
-
-6906B67A-2CE8-4625-90CF-1DFF4F5D5111,ExitType
-{Finish}
-
-69663474-61D6-4E8B-B4B8-288E40DEFADD,CheckCondition
-{Before Action is Executed}
-
-69663474-61D6-4E8B-B4B8-288E40DEFADD,String
-{<%GuiMode%>}
-
-6967FABA-B1BD-4160-845A-D3582B8967A9,CheckCondition
-{Before Action is Executed}
-
-6967FABA-B1BD-4160-845A-D3582B8967A9,String
-{<%ViewReadme%>}
-
-697B5256-7EBA-4599-B533-6B1A540B5961,Action
-{Install Actions}
-
-6AED9C7F-5DEE-4356-A1E3-8D9B67EA577A,CheckCondition
-{Before Action is Executed}
-
-6AED9C7F-5DEE-4356-A1E3-8D9B67EA577A,Operator
-{false}
-
-6AED9C7F-5DEE-4356-A1E3-8D9B67EA577A,String
-{<%InstallStopped%>}
-
-72AECA62-50D3-4BBE-87B8-9B7528CCC7BD,CheckCondition
-{Before Action is Executed}
-
-72AECA62-50D3-4BBE-87B8-9B7528CCC7BD,Operator
-{false}
-
-72AECA62-50D3-4BBE-87B8-9B7528CCC7BD,String
-{<%Answer%>}
-
-7377E888-711F-4C79-B921-2A1A3F1F0CA5,Default
-{Yes}
-
-7377E888-711F-4C79-B921-2A1A3F1F0CA5,Prompt
-{<%UninstallStartupText%>}
-
-7571DE26-7BDF-456E-86DE-FE9362FFB11C,CheckCondition
-{Before Action is Executed}
-
-7571DE26-7BDF-456E-86DE-FE9362FFB11C,Operator
-{false}
-
-7571DE26-7BDF-456E-86DE-FE9362FFB11C,String
-{<%UpgradeInstall%>}
-
-7729A27E-04C9-4ED3-9F8E-5971CC308F51,ExitType
-{Finish}
-
-78B27C47-637A-42F5-934A-4EFA6164572F,CheckCondition
-{Before Action is Executed}
-
-78B27C47-637A-42F5-934A-4EFA6164572F,String
-{<%GuiMode%>}
-
-78E848D0-C357-4F8D-9A49-2F1C075E8C87,CheckCondition
-{Before Action is Executed}
-
-78E848D0-C357-4F8D-9A49-2F1C075E8C87,Filename
-{<%ProgramExecutable%>}
-
-7B9998FD-0077-4EC1-8F96-88ABBE05A702,Background
-{#FFFFFF}
-
-7B9998FD-0077-4EC1-8F96-88ABBE05A702,Text,subst
-{1}
-
-7B9998FD-0077-4EC1-8F96-88ABBE05A702,Type
-{checkbutton}
-
-7B9998FD-0077-4EC1-8F96-88ABBE05A702,VirtualText
-{CreateQuickLaunchShortcut}
-
-7B9998FD-0077-4EC1-8F96-88ABBE05A702,X
-{185}
-
-7B9998FD-0077-4EC1-8F96-88ABBE05A702,Y
-{200}
-
-7BF3B571-3EE3-485C-BFB9-3732B067B63F,CheckCondition
-{Before Action is Executed}
-
-7BF3B571-3EE3-485C-BFB9-3732B067B63F,Message,subst
-{1}
-
-7BF3B571-3EE3-485C-BFB9-3732B067B63F,Title,subst
-{1}
-
-7BF3B571-3EE3-485C-BFB9-3732B067B63F,TrueValue
-{No}
-
-7C59ECDE-E0B1-4B3B-9ADE-364347F153E1,FileName
-{<%ShortAppName%>-uninstall}
-
-7C59ECDE-E0B1-4B3B-9ADE-364347F153E1,ShortcutName
-{Uninstall <%AppName%>}
-
-7C59ECDE-E0B1-4B3B-9ADE-364347F153E1,TargetFileName
-{<%Uninstaller%>}
-
-7C59ECDE-E0B1-4B3B-9ADE-364347F153E1,WorkingDirectory
-{<%InstallDir%>}
-
-7D1EBBDC-5691-4044-BB19-6CE7D704D434,CheckCondition
-{Before Action is Executed}
-
-7D1EBBDC-5691-4044-BB19-6CE7D704D434,Filename
-{<%ProgramReadme%>}
-
-7D8589FC-AF69-4DFB-9DE3-363BAFB40C0D,Destination
-{<%InstallDir%>}
-
-7D8589FC-AF69-4DFB-9DE3-363BAFB40C0D,FileSize
-{49475988}
-
-7D8589FC-AF69-4DFB-9DE3-363BAFB40C0D,Name
-{Program Files}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,BackButton,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,BrowseButton,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,BrowseText,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,CancelButton,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,Caption,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,Destination,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,DestinationLabel,subst
-{0}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,Message,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,NextButton,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,Subtitle,subst
-{1}
-
-806BCA96-55DE-4551-BD24-7BDA967C0122,Title,subst
-{1}
-
-822B14AA-B0F0-4CFD-9E69-C5ACA1946599,CheckCondition
-{Before Action is Executed}
-
-822B14AA-B0F0-4CFD-9E69-C5ACA1946599,Platform
-{Windows}
-
-863CE846-BD62-448A-BEC7-DAFF4E9C61A4,CheckCondition
-{Before Action is Executed}
-
-863CE846-BD62-448A-BEC7-DAFF4E9C61A4,Operator
-{false}
-
-863CE846-BD62-448A-BEC7-DAFF4E9C61A4,String
-{<%InstallStopped%>}
-
-8908F64E-3013-478A-8CB2-A232E4E56511,Default
-{Yes}
-
-8908F64E-3013-478A-8CB2-A232E4E56511,Prompt
-{<%InstallStartupText%>}
-
-89C128FC-A7C5-462B-ACAD-DFAF81BE1B4B,ExecuteAction
-{Before Pane is Displayed}
-
-89C128FC-A7C5-462B-ACAD-DFAF81BE1B4B,State
-{disabled}
-
-89C128FC-A7C5-462B-ACAD-DFAF81BE1B4B,Widget
-{Back Button;Next Button}
-
-8B1A41D1-6E41-41B4-80F6-15493100F9FC,ShortcutName
-{<%AppName%>}
-
-8B1A41D1-6E41-41B4-80F6-15493100F9FC,TargetFileName
-{<%ProgramExecutable%>}
-
-8B1A41D1-6E41-41B4-80F6-15493100F9FC,WorkingDirectory
-{<%InstallDir%>}
-
-8DA0CB01-C65E-4F61-842C-38C1AD3087C1,CheckCondition
-{Before Action is Executed}
-
-8DA0CB01-C65E-4F61-842C-38C1AD3087C1,Filename
-{<%ProgramExecutable%>}
-
-9688B263-B6EC-40CD-9B31-0D1843879A88,CheckCondition
-{Before Action is Executed}
-
-9688B263-B6EC-40CD-9B31-0D1843879A88,Filename
-{<%ProgramExecutable%>}
-
-96B93C29-6883-4B14-A4BB-063769CC7311,Background
-{#FFFFFF}
-
-96B93C29-6883-4B14-A4BB-063769CC7311,Text,subst
-{1}
-
-96B93C29-6883-4B14-A4BB-063769CC7311,Type
-{checkbutton}
-
-96B93C29-6883-4B14-A4BB-063769CC7311,VirtualText
-{LaunchApplication}
-
-96B93C29-6883-4B14-A4BB-063769CC7311,X
-{185}
-
-96B93C29-6883-4B14-A4BB-063769CC7311,Y
-{160}
-
-96D47F15-DF92-4ED1-B3B7-568EBD24CB9B,Alias
-{Install Actions}
-
-A0694726-591A-4EBA-AAD2-075E5F9091EE,BackButton,subst
-{1}
-
-A0694726-591A-4EBA-AAD2-075E5F9091EE,CancelButton,subst
-{1}
-
-A0694726-591A-4EBA-AAD2-075E5F9091EE,Caption,subst
-{1}
-
-A0694726-591A-4EBA-AAD2-075E5F9091EE,Message,subst
-{1}
-
-A0694726-591A-4EBA-AAD2-075E5F9091EE,NextButton,subst
-{1}
-
-A5D3BBB1-85B1-4EB2-B853-8B55062277FC,CheckCondition
-{Before Next Pane is Displayed}
-
-A5D3BBB1-85B1-4EB2-B853-8B55062277FC,FailureMessage
-{<%DirectoryPermissionText%>}
-
-A5D3BBB1-85B1-4EB2-B853-8B55062277FC,Filename
-{<%InstallDir%>}
-
-A5D3BBB1-85B1-4EB2-B853-8B55062277FC,Permission
-{can create}
-
-AC727B92-7C03-4C07-9E07-CADE9DF02694,Alias
-{Cancel Actions}
-
-AD493088-BC25-4A05-83FF-CAED2A7960F0,ProgramCommandLine
-{<%ProgramExecutable%>}
-
-AD493088-BC25-4A05-83FF-CAED2A7960F0,WaitForProgram
-{No}
-
-AD493088-BC25-4A05-83FF-CAED2A7960F0,WorkingDirectory
-{<%InstallDir%>}
-
-AF5F4D19-561B-4E81-8A7A-D0C9DCEE7487,ShortcutDirectory
-{<%QUICK_LAUNCH%>}
-
-AF5F4D19-561B-4E81-8A7A-D0C9DCEE7487,ShortcutName
-{<%AppName%>}
-
-AF5F4D19-561B-4E81-8A7A-D0C9DCEE7487,TargetFileName
-{<%ProgramExecutable%>}
-
-AF5F4D19-561B-4E81-8A7A-D0C9DCEE7487,WorkingDirectory
-{<%InstallDir%>}
-
-AIX-ppc,Active
-{No}
-
-AIX-ppc,BuildSeparateArchives
-{No}
-
-AIX-ppc,DefaultDirectoryPermission
-{0755}
-
-AIX-ppc,DefaultFilePermission
-{0755}
-
-AIX-ppc,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-AIX-ppc,FallBackToConsole
-{Yes}
-
-AIX-ppc,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-AIX-ppc,InstallMode
-{Standard}
-
-AIX-ppc,InstallType
-{Typical}
-
-AIX-ppc,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-AIX-ppc,ProgramFolderAllUsers
-{No}
-
-AIX-ppc,ProgramFolderName
-{<%AppName%>}
-
-AIX-ppc,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-AIX-ppc,ProgramName
-{}
-
-AIX-ppc,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-AIX-ppc,PromptForRoot
-{Yes}
-
-AIX-ppc,RequireRoot
-{No}
-
-AIX-ppc,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-B41385EA-BF12-40E2-8C63-8D34C9085CB8,BackButton,subst
-{1}
-
-B41385EA-BF12-40E2-8C63-8D34C9085CB8,CancelButton,subst
-{1}
-
-B41385EA-BF12-40E2-8C63-8D34C9085CB8,Caption,subst
-{1}
-
-B41385EA-BF12-40E2-8C63-8D34C9085CB8,Message,subst
-{1}
-
-B41385EA-BF12-40E2-8C63-8D34C9085CB8,NextButton,subst
-{1}
-
-B5EB2E45-BECA-421A-A2F1-1A1D03FBA8E5,Prompt
-{<%ConsoleSelectDestinationText%>}
-
-B5EB2E45-BECA-421A-A2F1-1A1D03FBA8E5,VirtualText
-{InstallDir}
-
-BB024539-5037-4A7B-95BE-26C233CD3842,Action
-{Uninstall Actions}
-
-BB52149D-79DD-4120-8BB8-FF9E81E79E42,Background
-{#FFFFFF}
-
-BB52149D-79DD-4120-8BB8-FF9E81E79E42,Text,subst
-{1}
-
-BB52149D-79DD-4120-8BB8-FF9E81E79E42,Type
-{checkbutton}
-
-BB52149D-79DD-4120-8BB8-FF9E81E79E42,VirtualText
-{CreateQuickLaunchShortcut}
-
-BB52149D-79DD-4120-8BB8-FF9E81E79E42,X
-{185}
-
-BB52149D-79DD-4120-8BB8-FF9E81E79E42,Y
-{200}
-
-BDBE9DC0-4E01-4B21-8ED7-680161E92902,Alias
-{Uninstall Actions}
-
-C1C77698-90D3-49DA-8751-B927F3649C44,CheckCondition
-{Before Action is Executed}
-
-C1C77698-90D3-49DA-8751-B927F3649C44,Filename
-{<%ProgramExecutable%>}
-
-C1DA1E92-56F3-4EFE-930E-852FEEF558BF,Action
-{Uninstall Actions}
-
-C5CB03C7-3D7A-4DC5-B335-FE84A9A1D18A,CheckCondition
-{Before Action is Executed}
-
-C5CB03C7-3D7A-4DC5-B335-FE84A9A1D18A,Operator
-{false}
-
-C5CB03C7-3D7A-4DC5-B335-FE84A9A1D18A,String
-{<%UpgradeInstall%>}
-
-CF5E291F-7B8C-4EA0-8A9B-1F7FAE3F58BD,Action
-{Install Actions}
-
-D361C7FD-0376-4C29-8E8F-4056D0F505B6,CheckCondition
-{Before Action is Executed}
-
-D361C7FD-0376-4C29-8E8F-4056D0F505B6,Filename
-{<%ProgramExecutable%>}
-
-D66EB685-05F6-4789-95CD-B3BF714CCFF6,CheckCondition
-{Before Next Action is Executed}
-
-D66EB685-05F6-4789-95CD-B3BF714CCFF6,FailureMessage
-{<%DirectoryPermissionText%>}
-
-D66EB685-05F6-4789-95CD-B3BF714CCFF6,Filename
-{<%InstallDir%>}
-
-D66EB685-05F6-4789-95CD-B3BF714CCFF6,Permission
-{can create}
-
-D7F35084-4D8C-4118-B96C-48FF9496BFE2,CheckCondition
-{Before Action is Executed}
-
-D7F35084-4D8C-4118-B96C-48FF9496BFE2,Filename
-{<%ProgramExecutable%>}
-
-D8F58F63-F6A3-4FF8-8579-5F58FD242EAC,Alias
-{Finish Actions}
-
-DA64DBB1-48C9-481E-A9B5-79114AE13ED3,Background
-{#FFFFFF}
-
-DA64DBB1-48C9-481E-A9B5-79114AE13ED3,Text,subst
-{1}
-
-DA64DBB1-48C9-481E-A9B5-79114AE13ED3,Type
-{checkbutton}
-
-DA64DBB1-48C9-481E-A9B5-79114AE13ED3,VirtualText
-{LaunchApplication}
-
-DA64DBB1-48C9-481E-A9B5-79114AE13ED3,X
-{185}
-
-DA64DBB1-48C9-481E-A9B5-79114AE13ED3,Y
-{160}
-
-DBF8F9B2-6265-4155-9984-8F6EEE64E634,CheckCondition
-{Before Action is Executed}
-
-DBF8F9B2-6265-4155-9984-8F6EEE64E634,String
-{<%GuiMode%>}
-
-DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF,Background
-{#FFFFFF}
-
-DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF,Text,subst
-{1}
-
-DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF,Type
-{checkbutton}
-
-DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF,VirtualText
-{ViewReadme}
-
-DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF,X
-{185}
-
-DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF,Y
-{140}
-
-E26CCBFE-A672-4B88-BD04-9A11859EDD42,String
-{<%ErrorsOccurred%>}
-
-E4CE56DE-497F-4D32-A6AC-2D03EBF05FD6,CheckCondition
-{Before Action is Executed}
-
-E4CE56DE-497F-4D32-A6AC-2D03EBF05FD6,Operator
-{false}
-
-E4CE56DE-497F-4D32-A6AC-2D03EBF05FD6,String
-{<%InstallStopped%>}
-
-E92E5657-B9EE-4F5A-9250-24EE1E67F387,CheckCondition
-{Before Action is Executed}
-
-E92E5657-B9EE-4F5A-9250-24EE1E67F387,Operator
-{false}
-
-E92E5657-B9EE-4F5A-9250-24EE1E67F387,String
-{<%InstallStopped%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message,subst
-{1}
-
-F114794C-193C-4E5D-8830-4BBF8FDF9FC8,CheckCondition
-{Before Action is Executed}
-
-F114794C-193C-4E5D-8830-4BBF8FDF9FC8,String
-{<%CreateQuickLaunchShortcut%>}
-
-F1F054C5-13AA-41B3-8BF3-79A7B63EBFC9,Action
-{Uninstall Actions}
-
-F212318C-2AAC-4B73-8A27-791B38F9D509,ExitType
-{Finish}
-
-F243AA46-CB2B-46BA-8254-0CE5192BFD7B,ShortcutName
-{<%AppName%>}
-
-F243AA46-CB2B-46BA-8254-0CE5192BFD7B,TargetFileName
-{<%ProgramExecutable%>}
-
-F243AA46-CB2B-46BA-8254-0CE5192BFD7B,WorkingDirectory
-{<%InstallDir%>}
-
-F97966F6-B23B-4C59-95BC-A0A06B71B2BF,CheckCondition
-{Before Action is Executed}
-
-F97966F6-B23B-4C59-95BC-A0A06B71B2BF,Filename
-{<%ProgramExecutable%>}
-
-FC22F272-B809-410E-A4C3-C3143C71FC94,BackButton,subst
-{1}
-
-FC22F272-B809-410E-A4C3-C3143C71FC94,CancelButton,subst
-{1}
-
-FC22F272-B809-410E-A4C3-C3143C71FC94,Caption,subst
-{1}
-
-FC22F272-B809-410E-A4C3-C3143C71FC94,Message,subst
-{1}
-
-FC22F272-B809-410E-A4C3-C3143C71FC94,NextButton,subst
-{1}
-
-FC22F272-B809-410E-A4C3-C3143C71FC94,Subtitle,subst
-{1}
-
-FC22F272-B809-410E-A4C3-C3143C71FC94,Text,subst
-{1}
-
-FC22F272-B809-410E-A4C3-C3143C71FC94,Title,subst
-{1}
-
-FreeBSD-4-x86,Active
-{No}
-
-FreeBSD-4-x86,BuildSeparateArchives
-{No}
-
-FreeBSD-4-x86,DefaultDirectoryPermission
-{0755}
-
-FreeBSD-4-x86,DefaultFilePermission
-{0755}
-
-FreeBSD-4-x86,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-FreeBSD-4-x86,FallBackToConsole
-{Yes}
-
-FreeBSD-4-x86,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-FreeBSD-4-x86,InstallMode
-{Standard}
-
-FreeBSD-4-x86,InstallType
-{Typical}
-
-FreeBSD-4-x86,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-FreeBSD-4-x86,ProgramFolderAllUsers
-{No}
-
-FreeBSD-4-x86,ProgramFolderName
-{<%AppName%>}
-
-FreeBSD-4-x86,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-FreeBSD-4-x86,ProgramName
-{}
-
-FreeBSD-4-x86,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-FreeBSD-4-x86,PromptForRoot
-{Yes}
-
-FreeBSD-4-x86,RequireRoot
-{No}
-
-FreeBSD-4-x86,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-FreeBSD-5-x86,Active
-{No}
-
-FreeBSD-5-x86,BuildSeparateArchives
-{No}
-
-FreeBSD-5-x86,DefaultDirectoryPermission
-{0755}
-
-FreeBSD-5-x86,DefaultFilePermission
-{0755}
-
-FreeBSD-5-x86,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-FreeBSD-5-x86,FallBackToConsole
-{Yes}
-
-FreeBSD-5-x86,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-FreeBSD-5-x86,InstallMode
-{Standard}
-
-FreeBSD-5-x86,InstallType
-{Typical}
-
-FreeBSD-5-x86,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-FreeBSD-5-x86,ProgramFolderAllUsers
-{No}
-
-FreeBSD-5-x86,ProgramFolderName
-{<%AppName%>}
-
-FreeBSD-5-x86,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-FreeBSD-5-x86,ProgramName
-{}
-
-FreeBSD-5-x86,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-FreeBSD-5-x86,PromptForRoot
-{Yes}
-
-FreeBSD-5-x86,RequireRoot
-{No}
-
-FreeBSD-5-x86,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-FreeBSD-6-x86,Active
-{No}
-
-FreeBSD-6-x86,BuildSeparateArchives
-{No}
-
-FreeBSD-6-x86,DefaultDirectoryPermission
-{0755}
-
-FreeBSD-6-x86,DefaultFilePermission
-{0755}
-
-FreeBSD-6-x86,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-FreeBSD-6-x86,FallBackToConsole
-{Yes}
-
-FreeBSD-6-x86,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-FreeBSD-6-x86,InstallMode
-{Standard}
-
-FreeBSD-6-x86,InstallType
-{Typical}
-
-FreeBSD-6-x86,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-FreeBSD-6-x86,ProgramFolderAllUsers
-{No}
-
-FreeBSD-6-x86,ProgramFolderName
-{<%AppName%>}
-
-FreeBSD-6-x86,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-FreeBSD-6-x86,ProgramName
-{}
-
-FreeBSD-6-x86,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-FreeBSD-6-x86,PromptForRoot
-{Yes}
-
-FreeBSD-6-x86,RequireRoot
-{No}
-
-FreeBSD-6-x86,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-FreeBSD-7-x86,Active
-{No}
-
-FreeBSD-7-x86,BuildSeparateArchives
-{No}
-
-FreeBSD-7-x86,DefaultDirectoryPermission
-{0755}
-
-FreeBSD-7-x86,DefaultFilePermission
-{0755}
-
-FreeBSD-7-x86,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-FreeBSD-7-x86,FallBackToConsole
-{Yes}
-
-FreeBSD-7-x86,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-FreeBSD-7-x86,InstallMode
-{Standard}
-
-FreeBSD-7-x86,InstallType
-{Typical}
-
-FreeBSD-7-x86,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-FreeBSD-7-x86,ProgramFolderAllUsers
-{No}
-
-FreeBSD-7-x86,ProgramFolderName
-{<%AppName%>}
-
-FreeBSD-7-x86,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-FreeBSD-7-x86,ProgramName
-{}
-
-FreeBSD-7-x86,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-FreeBSD-7-x86,PromptForRoot
-{Yes}
-
-FreeBSD-7-x86,RequireRoot
-{No}
-
-FreeBSD-7-x86,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-HPUX-hppa,Active
-{No}
-
-HPUX-hppa,BuildSeparateArchives
-{No}
-
-HPUX-hppa,DefaultDirectoryPermission
-{0755}
-
-HPUX-hppa,DefaultFilePermission
-{0755}
-
-HPUX-hppa,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-HPUX-hppa,FallBackToConsole
-{Yes}
-
-HPUX-hppa,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-HPUX-hppa,InstallMode
-{Standard}
-
-HPUX-hppa,InstallType
-{Typical}
-
-HPUX-hppa,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-HPUX-hppa,ProgramFolderAllUsers
-{No}
-
-HPUX-hppa,ProgramFolderName
-{<%AppName%>}
-
-HPUX-hppa,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-HPUX-hppa,ProgramName
-{}
-
-HPUX-hppa,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-HPUX-hppa,PromptForRoot
-{Yes}
-
-HPUX-hppa,RequireRoot
-{No}
-
-HPUX-hppa,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-Linux-x86,Active
-{No}
-
-Linux-x86,BuildSeparateArchives
-{No}
-
-Linux-x86,DefaultDirectoryPermission
-{0755}
-
-Linux-x86,DefaultFilePermission
-{0755}
-
-Linux-x86,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-Linux-x86,FallBackToConsole
-{Yes}
-
-Linux-x86,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-Linux-x86,InstallMode
-{Standard}
-
-Linux-x86,InstallType
-{Typical}
-
-Linux-x86,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-Linux-x86,ProgramFolderAllUsers
-{No}
-
-Linux-x86,ProgramFolderName
-{<%AppName%>}
-
-Linux-x86,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-Linux-x86,ProgramName
-{}
-
-Linux-x86,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-Linux-x86,PromptForRoot
-{Yes}
-
-Linux-x86,RequireRoot
-{No}
-
-Linux-x86,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-Linux-x86_64,Active
-{No}
-
-Linux-x86_64,BuildSeparateArchives
-{No}
-
-Linux-x86_64,DefaultDirectoryPermission
-{0755}
-
-Linux-x86_64,DefaultFilePermission
-{0755}
-
-Linux-x86_64,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-Linux-x86_64,FallBackToConsole
-{Yes}
-
-Linux-x86_64,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-Linux-x86_64,InstallMode
-{Standard}
-
-Linux-x86_64,InstallType
-{Typical}
-
-Linux-x86_64,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-Linux-x86_64,ProgramFolderAllUsers
-{No}
-
-Linux-x86_64,ProgramFolderName
-{<%AppName%>}
-
-Linux-x86_64,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-Linux-x86_64,ProgramName
-{}
-
-Linux-x86_64,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-Linux-x86_64,PromptForRoot
-{Yes}
-
-Linux-x86_64,RequireRoot
-{No}
-
-Linux-x86_64,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-Solaris-sparc,Active
-{No}
-
-Solaris-sparc,BuildSeparateArchives
-{No}
-
-Solaris-sparc,DefaultDirectoryPermission
-{0755}
-
-Solaris-sparc,DefaultFilePermission
-{0755}
-
-Solaris-sparc,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-Solaris-sparc,FallBackToConsole
-{Yes}
-
-Solaris-sparc,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-Solaris-sparc,InstallMode
-{Standard}
-
-Solaris-sparc,InstallType
-{Typical}
-
-Solaris-sparc,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-Solaris-sparc,ProgramFolderAllUsers
-{No}
-
-Solaris-sparc,ProgramFolderName
-{<%AppName%>}
-
-Solaris-sparc,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-Solaris-sparc,ProgramName
-{}
-
-Solaris-sparc,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-Solaris-sparc,PromptForRoot
-{Yes}
-
-Solaris-sparc,RequireRoot
-{No}
-
-Solaris-sparc,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-Solaris-x86,Active
-{No}
-
-Solaris-x86,BuildSeparateArchives
-{No}
-
-Solaris-x86,DefaultDirectoryPermission
-{0755}
-
-Solaris-x86,DefaultFilePermission
-{0755}
-
-Solaris-x86,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-Solaris-x86,FallBackToConsole
-{Yes}
-
-Solaris-x86,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-Solaris-x86,InstallMode
-{Standard}
-
-Solaris-x86,InstallType
-{Typical}
-
-Solaris-x86,ProgramExecutable
-{<%InstallDir%>/EasyMercurial}
-
-Solaris-x86,ProgramFolderAllUsers
-{No}
-
-Solaris-x86,ProgramFolderName
-{<%AppName%>}
-
-Solaris-x86,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-Solaris-x86,ProgramName
-{}
-
-Solaris-x86,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-Solaris-x86,PromptForRoot
-{Yes}
-
-Solaris-x86,RequireRoot
-{No}
-
-Solaris-x86,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-TarArchive,Active
-{No}
-
-TarArchive,BuildSeparateArchives
-{No}
-
-TarArchive,CompressionLevel
-{6}
-
-TarArchive,DefaultDirectoryPermission
-{0755}
-
-TarArchive,DefaultFilePermission
-{0755}
-
-TarArchive,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-TarArchive,FallBackToConsole
-{Yes}
-
-TarArchive,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-TarArchive,InstallMode
-{Standard}
-
-TarArchive,InstallType
-{Typical}
-
-TarArchive,OutputFileName
-{<%ShortAppName%>-<%Version%>.tar.gz}
-
-TarArchive,ProgramExecutable
-{}
-
-TarArchive,ProgramFolderAllUsers
-{No}
-
-TarArchive,ProgramFolderName
-{<%AppName%>}
-
-TarArchive,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-TarArchive,ProgramName
-{}
-
-TarArchive,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-TarArchive,PromptForRoot
-{Yes}
-
-TarArchive,RequireRoot
-{No}
-
-TarArchive,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-TarArchive,VirtualTextMap
-{<%InstallDir%> <%ShortAppName%>}
-
-Windows,Active
-{Yes}
-
-Windows,BuildSeparateArchives
-{No}
-
-Windows,Executable
-{<%AppName%>-<%Version%>-Setup<%Ext%>}
-
-Windows,FileDescription
-{<%AppName%> <%Version%> Setup}
-
-Windows,IncludeTWAPI
-{No}
-
-Windows,InstallDir
-{<%PROGRAM_FILES%>/<%AppName%>}
-
-Windows,InstallMode
-{Standard}
-
-Windows,InstallType
-{Typical}
-
-Windows,LastRequireAdministrator
-{Yes}
-
-Windows,ProgramExecutable
-{<%InstallDir%>/EasyMercurial.exe}
-
-Windows,ProgramFolderAllUsers
-{No}
-
-Windows,ProgramFolderName
-{<%AppName%>}
-
-Windows,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-Windows,ProgramName
-{}
-
-Windows,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-Windows,RequireAdministrator
-{Yes}
-
-Windows,UseUncompressedBinaries
-{No}
-
-Windows,WindowsIcon
-{Setup Blue Screen.ico}
-
-ZipArchive,Active
-{No}
-
-ZipArchive,BuildSeparateArchives
-{No}
-
-ZipArchive,CompressionLevel
-{6}
-
-ZipArchive,DefaultDirectoryPermission
-{0755}
-
-ZipArchive,DefaultFilePermission
-{0755}
-
-ZipArchive,Executable
-{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
-
-ZipArchive,FallBackToConsole
-{Yes}
-
-ZipArchive,InstallDir
-{<%Home%>/<%ShortAppName%>}
-
-ZipArchive,InstallMode
-{Standard}
-
-ZipArchive,InstallType
-{Typical}
-
-ZipArchive,OutputFileName
-{<%ShortAppName%>-<%Version%>.zip}
-
-ZipArchive,ProgramExecutable
-{}
-
-ZipArchive,ProgramFolderAllUsers
-{No}
-
-ZipArchive,ProgramFolderName
-{<%AppName%>}
-
-ZipArchive,ProgramLicense
-{<%InstallDir%>/LICENSE.txt}
-
-ZipArchive,ProgramName
-{}
-
-ZipArchive,ProgramReadme
-{<%InstallDir%>/README.txt}
-
-ZipArchive,PromptForRoot
-{Yes}
-
-ZipArchive,RequireRoot
-{No}
-
-ZipArchive,RootInstallDir
-{/usr/local/<%ShortAppName%>}
-
-ZipArchive,VirtualTextMap
-{<%InstallDir%> <%ShortAppName%>}
-
-}
-
-::msgcat::mcmset ca {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset cs {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset de {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset en {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-0B08768B-E5AA-4701-B9CE-424C9F6AA96B,Description
-{<%ProgramFilesDescription%>}
-
-18C3EF37-5C55-43E6-A223-7A4444357E2F,Text
-{<%ViewReadmeText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-1ED68248-972F-4343-BEF4-C91ED3618601,Text
-{<%CreateDesktopShortcutText%>}
-
-2013F392-6A72-4E50-8294-F83C8DBECFEA,Message
-{<%UninstallStartupText%>}
-
-2013F392-6A72-4E50-8294-F83C8DBECFEA,Title
-{<%UninstallApplicationText%>}
-
-2A3B15B2-D3B0-43DD-9FDD-581278BD0EA6,Description
-{<%CustomInstallDescription%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-4629322A-B41A-41FD-976D-987B375AFECC,Caption
-{<%ApplicationReadmeText%>}
-
-4629322A-B41A-41FD-976D-987B375AFECC,Message
-{}
-
-4629322A-B41A-41FD-976D-987B375AFECC,Title
-{<%ApplicationReadmeText%>}
-
-489BB600-70D6-4EC8-B032-F5FF6FDD532F,Description
-{<%TypicalInstallDescription%>}
-
-5F872846-216A-4EAE-B600-15CBEE8DF4CF,Text
-{<%CreateDesktopShortcutText%>}
-
-7B9998FD-0077-4EC1-8F96-88ABBE05A702,Text
-{<%CreateQuickLaunchShortcutText%>}
-
-7BF3B571-3EE3-485C-BFB9-3732B067B63F,Message
-{<%InstallStartupText%>}
-
-7BF3B571-3EE3-485C-BFB9-3732B067B63F,Title
-{<%InstallApplicationText%>}
-
-96B93C29-6883-4B14-A4BB-063769CC7311,Text
-{<%LaunchApplicationText%>}
-
-BB52149D-79DD-4120-8BB8-FF9E81E79E42,Text
-{<%CreateQuickLaunchShortcutText%>}
-
-DA64DBB1-48C9-481E-A9B5-79114AE13ED3,Text
-{<%LaunchApplicationText%>}
-
-DDC5D1FE-8AB6-43C5-8CE2-5C71729E8BFF,Text
-{<%ViewReadmeText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset es {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset fr {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset hu {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset it {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset lt {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset nl {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset pl {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset pt_br {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-::msgcat::mcmset ru {
-09720EAD-E327-41AC-B5C4-CAB372D427D8,Message
-{<%UninstallingApplicationText%>}
-
-1D532C82-EEEA-4AA3-B14B-D0F8AD35DBF2,Message
-{<%InstallationCompleteText%>}
-
-446444AE-E509-41B3-92E1-6B6EFFA1FD12,Message
-{<%UninstallCompleteText%>}
-
-EAE9FD9C-689E-40D0-9A06-BBAD9F22D0B1,Message
-{<%InstallingApplicationText%>}
-
-}
-
--- a/easyhg-extdiff.sh	Tue Mar 15 12:36:26 2011 +0000
+++ b/easyhg-extdiff.sh	Thu Dec 06 13:54:34 2018 +0000
@@ -7,7 +7,7 @@
 while [ $# -gt 2 ]; do
     shift
 done
-for d in kdiff3 kdiff3.exe; do
+for d in easyhg-kdiff3 easyhg-kdiff3.exe kdiff3 kdiff3.exe; do
     if [ -x "$p/$d" ]; then
 	exec "$p/$d" "$1" "$2"
     elif [ -x "$(type -path $d)" ]; then
Binary file easyhg-icon.icns has changed
Binary file easyhg-icon.ico has changed
--- a/easyhg-merge.bat	Tue Mar 15 12:36:26 2011 +0000
+++ b/easyhg-merge.bat	Thu Dec 06 13:54:34 2018 +0000
@@ -18,5 +18,5 @@
 if "%found%"=="" (
     echo. "Failed to find kdiff.exe in path"
 ) else (
-    "%found%" "%~2" "%~1" "%~3" -o "%~1"
+    "%found%" "%~2" "%~1" "%~3" -o "%~1" --auto -L1 "%~1 (Common ancestor)" -L2 "%~1 (Your current version)" -L3 "%~1 (Version being merged)"
 )
--- a/easyhg-merge.sh	Tue Mar 15 12:36:26 2011 +0000
+++ b/easyhg-merge.sh	Thu Dec 06 13:54:34 2018 +0000
@@ -12,11 +12,16 @@
 left="$1"
 ancestor="$2"
 right="$3"
-for d in kdiff3 kdiff3.exe; do
-    if [ -x "$p/$d" ]; then
-	exec "$p/$d" "$ancestor" "$left" "$right" -o "$out"
-    elif [ -x "$(type -path $d)" ]; then
-	exec "$d" "$ancestor" "$left" "$right" -o "$out"
+for d in easyhg-kdiff3 easyhg-kdiff3.exe kdiff3 kdiff3.exe; do
+    exe="$p/$d"
+    if [ ! -x "$exe" ]; then
+	exe="$(type -path $d)"
+	if [ ! -x "$exe" ]; then
+	    exe=""
+	fi
+    fi
+    if [ -n "$exe" ]; then
+	exec "$exe" --auto "$ancestor" "$left" "$right" --output "$out" --auto -L1 "`basename $left` (Common ancestor)" -L2 "$left (Your current version)" -L3 "`basename $left` (Version being merged)"
     fi
 done
 fm=/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge
Binary file easyhg.icns has changed
Binary file easyhg.ico has changed
--- a/easyhg.pro	Tue Mar 15 12:36:26 2011 +0000
+++ b/easyhg.pro	Thu Dec 06 13:54:34 2018 +0000
@@ -4,18 +4,12 @@
 TEMPLATE = app
 TARGET = EasyMercurial
 
-# We use the 10.4 SDK and Carbon for all 32-bit OS/X,
-# and 10.6 with Cocoa for all 64-bit
-macx-g++40 {
-    # Note, to use the 10.4 SDK on 10.6+ you need qmake -spec macx-g++40
-    QMAKE_MAC_SDK = /Developer/SDKs/MacOSX10.4u.sdk
-    QMAKE_CFLAGS += -mmacosx-version-min=10.4
-    QMAKE_CXXFLAGS += -mmacosx-version-min=10.4
-    CONFIG += x86 ppc 
-}
-macx-g++ {
-    QMAKE_MAC_SDK = /Developer/SDKs/MacOSX10.6.sdk
+QT += widgets
+
+macx-llvm {
     CONFIG += x86_64
+    QMAKE_CFLAGS += -mmacosx-version-min=10.6
+    QMAKE_CXXFLAGS += -mmacosx-version-min=10.6
 }
 
 unix {
@@ -27,86 +21,118 @@
 OBJECTS_DIR = o
 MOC_DIR = o
 
-HEADERS = mainwindow.h \
-    hgtabwidget.h \
-    common.h \
-    grapher.h \
-    hgrunner.h \
-    changeset.h \
-    changesetitem.h \
-    changesetdetailitem.h \
-    logparser.h \
-    panner.h \
-    panned.h \
-    connectionitem.h \
-    textabbrev.h \
-    dateitem.h \
-    colourset.h \
-    debug.h \
-    recentfiles.h \
-    startupdialog.h \
-    repositorydialog.h \
-    multichoicedialog.h \
-    selectablelabel.h \
-    filestates.h \
-    filestatuswidget.h \
-    confirmcommentdialog.h \
-    hgaction.h \
-    historywidget.h \
-    changesetscene.h \
-    incomingdialog.h \
-    uncommitteditem.h \
-    settingsdialog.h \
-    clickablelabel.h \
-    workstatuswidget.h \
-    moreinformationdialog.h \
-    annotatedialog.h
-SOURCES = main.cpp \
-    mainwindow.cpp \
-    hgtabwidget.cpp \
-    hgrunner.cpp \
-    grapher.cpp \
-    common.cpp \
-    changeset.cpp \
-    changesetdetailitem.cpp \
-    changesetitem.cpp \
-    logparser.cpp \
-    panner.cpp \
-    panned.cpp \
-    connectionitem.cpp \
-    textabbrev.cpp \
-    dateitem.cpp \
-    colourset.cpp \
-    debug.cpp \
-    recentfiles.cpp \
-    startupdialog.cpp \
-    repositorydialog.cpp \
-    multichoicedialog.cpp \
-    selectablelabel.cpp \
-    filestates.cpp \
-    filestatuswidget.cpp \
-    confirmcommentdialog.cpp \
-    historywidget.cpp \
-    changesetscene.cpp \
-    incomingdialog.cpp \
-    uncommitteditem.cpp \
-    settingsdialog.cpp \
-    workstatuswidget.cpp \
-    moreinformationdialog.cpp \
-    annotatedialog.cpp
+HEADERS = \
+    src/mainwindow.h \
+    src/hgtabwidget.h \
+    src/common.h \
+    src/grapher.h \
+    src/hgrunner.h \
+    src/changeset.h \
+    src/changesetitem.h \
+    src/changesetdetailitem.h \
+    src/logparser.h \
+    src/panner.h \
+    src/panned.h \
+    src/connectionitem.h \
+    src/textabbrev.h \
+    src/colourset.h \
+    src/debug.h \
+    src/recentfiles.h \
+    src/startupdialog.h \
+    src/repositorydialog.h \
+    src/multichoicedialog.h \
+    src/selectablelabel.h \
+    src/filestates.h \
+    src/filestatuswidget.h \
+    src/confirmcommentdialog.h \
+    src/hgaction.h \
+    src/historywidget.h \
+    src/changesetscene.h \
+    src/changesetview.h \
+    src/incomingdialog.h \
+    src/uncommitteditem.h \
+    src/settingsdialog.h \
+    src/clickablelabel.h \
+    src/workstatuswidget.h \
+    src/moreinformationdialog.h \
+    src/annotatedialog.h \
+    src/hgignoredialog.h \
+    src/versiontester.h \
+    src/squeezedlabel.h \
+    src/fswatcher.h \
+    src/findwidget.h
+SOURCES = \
+    src/main.cpp \
+    src/mainwindow.cpp \
+    src/hgtabwidget.cpp \
+    src/hgrunner.cpp \
+    src/grapher.cpp \
+    src/common.cpp \
+    src/changeset.cpp \
+    src/changesetdetailitem.cpp \
+    src/changesetitem.cpp \
+    src/logparser.cpp \
+    src/panner.cpp \
+    src/panned.cpp \
+    src/connectionitem.cpp \
+    src/textabbrev.cpp \
+    src/colourset.cpp \
+    src/debug.cpp \
+    src/recentfiles.cpp \
+    src/startupdialog.cpp \
+    src/repositorydialog.cpp \
+    src/multichoicedialog.cpp \
+    src/selectablelabel.cpp \
+    src/filestates.cpp \
+    src/filestatuswidget.cpp \
+    src/confirmcommentdialog.cpp \
+    src/historywidget.cpp \
+    src/changesetscene.cpp \
+    src/changesetview.cpp \
+    src/incomingdialog.cpp \
+    src/uncommitteditem.cpp \
+    src/settingsdialog.cpp \
+    src/workstatuswidget.cpp \
+    src/moreinformationdialog.cpp \
+    src/annotatedialog.cpp \
+    src/hgignoredialog.cpp \
+    src/versiontester.cpp \
+    src/squeezedlabel.cpp \
+    src/fswatcher.cpp \
+    src/findwidget.cpp
+
 
 macx-* {
-    SOURCES += common_osx.mm
-    LIBS += -framework Foundation
-    ICON = easyhg.icns
+    OBJECTIVE_SOURCES += src/common_osx.mm
+    LIBS += -framework CoreServices -framework Foundation
+    ICON = easyhg-icon.icns
 }
 
 linux* {
     LIBS += -lutil
+    binaries.path = /usr/local/bin
+    binaries.files = EasyMercurial easyhg-extdiff.sh easyhg-merge.sh 
+    scripts.path = /usr/local/bin
+    scripts.files = easyhg-extdiff.sh easyhg-merge.sh
+    desktop.path = /usr/local/share/applications
+    desktop.files = deploy/linux/EasyMercurial.desktop
+    icon128.path = /usr/local/share/icons/hicolor/128x128/apps
+    icon128.files = images/icon/128/easyhg-icon.png
+    icon64.path = /usr/local/share/icons/hicolor/64x64/apps
+    icon64.files = images/icon/64/easyhg-icon.png
+    icon48.path = /usr/local/share/icons/hicolor/48x48/apps
+    icon48.files = images/icon/48/easyhg-icon.png
+    icon32.path = /usr/local/share/icons/hicolor/32x32/apps
+    icon32.files = images/icon/32/easyhg-icon.png
+    icon24.path = /usr/local/share/icons/hicolor/24x24/apps
+    icon24.files = images/icon/24/easyhg-icon.png
+    iconsc.path = /usr/local/share/icons/hicolor/scalable/apps
+    iconsc.files = images/icon/scalable/easyhg-icon.svg
+    INSTALLS += binaries desktop icon128 icon64 icon48 icon32 icon24 iconsc
 }
 
 win* {
-    LIBS += -lSecur32
+    LIBS += -lSecur32 -lAdvapi32
 }
 
 RESOURCES = easyhg.qrc
--- a/easyhg.py	Tue Mar 15 12:36:26 2011 +0000
+++ b/easyhg.py	Thu Dec 06 13:54:34 2018 +0000
@@ -4,8 +4,8 @@
 #
 #    Based on hgExplorer by Jari Korhonen
 #    Copyright (c) 2010 Jari Korhonen
-#    Copyright (c) 2010 Chris Cannam
-#    Copyright (c) 2010 Queen Mary, University of London
+#    Copyright (c) 2010-2012 Chris Cannam
+#    Copyright (c) 2010-2012 Queen Mary, University of London
 #    
 #    This program is free software; you can redistribute it and/or
 #    modify it under the terms of the GNU General Public License as
@@ -13,9 +13,14 @@
 #    License, or (at your option) any later version.  See the file
 #    COPYING included with this distribution for more information.
 
-import sys
-from mercurial import ui, getpass, util
+import sys, os, stat, urllib, urllib2, urlparse, hashlib
+
 from mercurial.i18n import _
+from mercurial import ui, util, error
+try:
+    from mercurial.url import passwordmgr
+except:
+    from mercurial.httprepo import passwordmgr
 
 # The value assigned here may be modified during installation, by
 # replacing its default value with another one.  We can't compare
@@ -24,9 +29,13 @@
 #
 easyhg_import_path = 'NO_EASYHG_IMPORT_PATH'
 if not easyhg_import_path.startswith('NO_'):
+    # We have an installation path: append it twice, once with
+    # the Python version suffixed
+    version_suffix = 'Py%d.%d' % (sys.version_info[0], sys.version_info[1])
+    sys.path.append(easyhg_import_path + "/" + version_suffix)
     sys.path.append(easyhg_import_path)
 
-# Try to load the PyQt4 module that we need.  If this fails, we should
+# Try to load the PyQt5 module that we need.  If this fails, we should
 # bail out later (in uisetup), because if we bail out now, Mercurial
 # will just continue without us and report success.  The invoking
 # application needs to be able to discover whether the module load
@@ -35,58 +44,324 @@
 #
 easyhg_pyqt_ok = True
 try:
-    from PyQt4 import QtGui
+    from PyQt5 import QtCore, QtGui, QtWidgets
 except ImportError:
     easyhg_pyqt_ok = False
+easyhg_qtapp = None
 
-easyhg_qtapp = None
+# These imports are optional, we just can't use the authfile (i.e.
+# "remember this password") feature without them
+#
+easyhg_authfile_imports_ok = True
+
+try:
+    from Crypto.Cipher import AES
+except ImportError:
+    print "EasyHg: Failed to import Crypto.Cipher module required for authfile support (try installing PyCrypto?)"
+    easyhg_authfile_imports_ok = False
+
+try:
+    import ConfigParser # Mercurial version won't write files
+    import base64
+except ImportError:
+    print "EasyHg: Failed to import modules (ConfigParser, base64) required for authfile support"
+    easyhg_authfile_imports_ok = False
+
+
+class EasyHgAuthStore(object):
+
+    def __init__(self, ui, url, user, passwd):
+
+        self.ui = ui
+        self.remote_url = url
+
+        self.user = user
+        self.passwd = passwd
+
+        self.auth_key = self.ui.config('easyhg', 'authkey')
+        self.auth_file = self.ui.config('easyhg', 'authfile')
+
+        self.use_auth_file = (easyhg_authfile_imports_ok and
+                              self.auth_key and self.auth_file)
+
+        self.auth_config = None
+        self.auth_cipher = None
+        self.remember = False
+
+        if self.use_auth_file:
+            self.auth_file = os.path.expanduser(self.auth_file)
+            self.load_auth_data()
+
+    def save(self):
+        if self.use_auth_file:
+            self.save_auth_data()
+    
+    def encrypt(self, utext):
+        iv = os.urandom(12)
+        text = utext.encode('utf-8')
+        text = '%s.%d.%s.easyhg' % (base64.b64encode(iv), len(text), text)
+        text += (16 - (len(text) % 16)) * ' '
+        cipher = AES.new(self.auth_key, AES.MODE_CBC, os.urandom(16))
+        ctext = base64.b64encode(cipher.encrypt(text))
+        return ctext
+
+    def decrypt(self, ctext):
+        try:
+            cipher = AES.new(self.auth_key, AES.MODE_CBC, os.urandom(16))
+            text = cipher.decrypt(base64.b64decode(ctext))
+            (iv, d, text) = text.partition('.')
+            (tlen, d, text) = text.partition('.')
+            return text[0:int(tlen)].decode('utf-8')
+        except:
+            self.ui.write("failed to decrypt/convert cached data!")
+            return ''
+
+    def argless_url(self):
+        parsed = urlparse.urlparse(self.remote_url)
+        return "%s://%s%s" % (parsed.scheme, parsed.netloc, parsed.path)
+
+    def pathless_url(self):
+        parsed = urlparse.urlparse(self.remote_url)
+        return "%s://%s" % (parsed.scheme, parsed.netloc)
+
+    def load_config(self):
+        if not self.auth_config:
+            self.auth_config = ConfigParser.RawConfigParser()
+        fp = None
+        try:
+            fp = open(self.auth_file)
+        except:
+            self.ui.write("unable to read authfile %s, ignoring\n" % self.auth_file)
+            return
+        self.auth_config.readfp(fp)
+        fp.close()
+
+    def save_config(self):
+        ofp = None
+        try:
+            ofp = open(self.auth_file, 'w')
+        except:
+            self.ui.write("failed to open authfile %s for writing\n" % self.auth_file)
+            raise
+        if os.name == 'posix':
+            try:
+                os.fchmod(ofp.fileno(), stat.S_IRUSR | stat.S_IWUSR)
+            except:
+                ofp.close()
+                self.ui.write("failed to set permissions on authfile %s\n" % self.auth_file)
+                raise
+        self.auth_config.write(ofp)
+        ofp.close()
+
+    def get_from_config(self, sect, key):
+        data = None
+        try:
+            data = self.auth_config.get(sect, key)
+        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+            pass
+        return data
+
+    def get_boolean_from_config(self, sect, key, deflt):
+        data = deflt
+        try:
+            data = self.auth_config.getboolean(sect, key)
+        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+            pass
+        return data
+
+    def set_to_config(self, sect, key, data):
+        if not self.auth_config.has_section(sect):
+            self.auth_config.add_section(sect)
+        self.auth_config.set(sect, key, data)
+
+    def remote_key(self, url, user):
+        # generate a "safe-for-config-file" key representing uri+user
+#        self.ui.write('generating remote_key for url %s and user %s\n' % (url, user))
+        s = '%s@@%s' % (url, user)
+        h = hashlib.sha1()
+        h.update(self.auth_key)
+        h.update(s)
+        hx = h.hexdigest()
+        return hx
+    
+    def remote_user_key(self):
+        return self.remote_key(self.pathless_url(), '')
+    
+    def remote_passwd_key(self):
+        return self.remote_key(self.pathless_url(), self.user)
+
+    def load_auth_data(self):
+
+        self.load_config()
+        if not self.auth_config: return
+
+        self.remember = self.get_boolean_from_config(
+            'preferences', 'remember', False)
+
+        if not self.user:
+            d = self.get_from_config('user', self.remote_user_key())
+            if d:
+                self.user = self.decrypt(d)
+
+        if self.user:
+            d = self.get_from_config('auth', self.remote_passwd_key())
+            if d:
+                self.passwd = self.decrypt(d)
+
+    def save_auth_data(self):
+
+        self.load_config()
+        if not self.auth_config: return
+    
+        self.set_to_config('preferences', 'remember', self.remember)
+
+#        self.ui.write('aiming to store details for user %s\n' % self.user)
+    
+        if self.remember and self.user:
+            d = self.encrypt(self.user)
+            self.set_to_config('user', self.remote_user_key(), d)
+        else:
+            self.set_to_config('user', self.remote_user_key(), '')
+    
+        if self.remember and self.user and self.passwd:
+            d = self.encrypt(self.passwd)
+            self.set_to_config('auth', self.remote_passwd_key(), d)
+        elif self.user:
+            self.set_to_config('auth', self.remote_passwd_key(), '')
+            
+        self.save_config()
+
+class EasyHgAuthDialog(object):
+
+    auth_store = None
+
+    def __init__(self, ui, url, user, passwd):
+        self.auth_store = EasyHgAuthStore(ui, url, user, passwd)
+
+    def ask(self, repeat):
+
+        if self.auth_store.user and self.auth_store.passwd and self.auth_store.remember:
+            if not repeat:
+                return (self.auth_store.user, self.auth_store.passwd)
+
+        dialog = QtWidgets.QDialog()
+        layout = QtWidgets.QGridLayout()
+        dialog.setLayout(layout)
+
+        heading = _('Login required')
+        if repeat:
+            heading = _('Login failed: please try again')
+        label_text = _(('<h3>%s</h3><p>Please provide your login details for the repository at<br><code>%s</code>:') % (heading, self.auth_store.argless_url()))
+        layout.addWidget(QtWidgets.QLabel(label_text), 0, 0, 1, 2)
+
+        user_field = QtWidgets.QLineEdit()
+        if self.auth_store.user: user_field.setText(self.auth_store.user)
+        layout.addWidget(QtWidgets.QLabel(_('User:')), 1, 0)
+        layout.addWidget(user_field, 1, 1)
+
+        passwd_field = QtWidgets.QLineEdit()
+        passwd_field.setEchoMode(QtWidgets.QLineEdit.Password)
+        if self.auth_store.passwd: passwd_field.setText(self.auth_store.passwd)
+        layout.addWidget(QtWidgets.QLabel(_('Password:')), 2, 0)
+        layout.addWidget(passwd_field, 2, 1)
+        user_field.textChanged.connect(passwd_field.clear)
+
+        remember_field = None
+        if self.auth_store.use_auth_file:
+            remember_field = QtWidgets.QCheckBox()
+            remember_field.setChecked(self.auth_store.remember)
+            remember_field.setText(_('Remember these details while EasyMercurial is running'))
+            layout.addWidget(remember_field, 3, 1)
+            warning_field = QtWidgets.QLabel()
+            warning_field.setText(_('<qt><i><small>Do not use this option if anyone else has access to your computer!</small></i><br></qt>'))
+            warning_field.hide()
+            remember_field.clicked.connect(warning_field.show)
+            layout.addWidget(warning_field, 4, 1, QtCore.Qt.AlignRight)
+
+        bb = QtWidgets.QDialogButtonBox()
+        ok = bb.addButton(bb.Ok)
+        cancel = bb.addButton(bb.Cancel)
+        cancel.setDefault(False)
+        cancel.setAutoDefault(False)
+        ok.setDefault(True)
+        ok.clicked.connect(dialog.accept)
+        cancel.clicked.connect(dialog.reject)
+        layout.addWidget(bb, 5, 0, 1, 2)
+
+        dialog.setWindowTitle(_('EasyMercurial: Login'))
+        dialog.show()
+
+        if not self.auth_store.user:
+            user_field.setFocus(True)
+        elif not self.auth_store.passwd:
+            passwd_field.setFocus(True)
+        else:
+            ok.setFocus(True)
+
+        dialog.raise_()
+        ok = dialog.exec_()
+        if not ok:
+            raise error.Abort(_('password entry cancelled'))
+
+        self.auth_store.user = user_field.text()
+        self.auth_store.passwd = passwd_field.text()
+
+        if remember_field:
+            self.auth_store.remember = remember_field.isChecked()
+    
+        self.auth_store.save()
+    
+        return (self.auth_store.user, self.auth_store.passwd)
+
 
 def uisetup(ui):
     if not easyhg_pyqt_ok:
-        raise util.Abort(_('Failed to load PyQt4 module required by easyhg.py'))
-    ui.__class__.prompt = easyhg_prompt
-    ui.__class__.getpass = easyhg_getpass
+        raise error.Abort(_('Failed to load PyQt5 module required by easyhg.py'))
     global easyhg_qtapp
-    easyhg_qtapp = QtGui.QApplication([])
+    easyhg_qtapp = QtWidgets.QApplication([])
 
-def easyhg_prompt(self, msg, default="y"):
-    if not self.interactive():
-        self.write(msg, ' ', default, "\n")
-        return default
-    if msg == _('user:'):
-        msg = _('User:')
-    d = QtGui.QInputDialog()
-    d.setInputMode(QtGui.QInputDialog.TextInput)
-    d.setTextEchoMode(QtGui.QLineEdit.Normal)
-    d.setLabelText(msg)
-    d.setWindowTitle(_('EasyMercurial: Information'))
-    d.show()
-    d.raise_()
-    ok = d.exec_()
-    r = d.textValue()
-    if not ok:
-        raise util.Abort(_('response expected'))
-    if not r:
-        return default
-    return r
+def monkeypatch_method(cls):
+    def decorator(func):
+        setattr(cls, func.__name__, func)
+        return func
+    return decorator
 
-def easyhg_getpass(self, prompt=None, default=None):
-    if not self.interactive():
-        return default
-    if not prompt or prompt == _('password:'):
-        prompt = _('Password:');
-    d = QtGui.QInputDialog()
-    d.setInputMode(QtGui.QInputDialog.TextInput)
-    d.setTextEchoMode(QtGui.QLineEdit.Password)
-    d.setLabelText(prompt)
-    d.setWindowTitle(_('EasyMercurial: Password'))
-    d.show()
-    d.raise_()
-    ok = d.exec_()
-    r = d.textValue()
-    if not ok:
-        raise util.Abort(_('response expected'))
-    if not r:
-        return default
-    return r
+orig_find = passwordmgr.find_user_password
 
+@monkeypatch_method(passwordmgr)
+def find_user_password(self, realm, authuri):
+
+    if not hasattr(self, '__easyhg_last'):
+        self.__easyhg_last = None
+
+    if not self.ui.interactive():
+        return orig_find(self, realm, authuri)
+    if not easyhg_pyqt_ok:
+        return orig_find(self, realm, authuri)
+
+    mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
+    authinfo = mgr.find_user_password(realm, authuri)
+    user, passwd = authinfo
+
+    repeat = False
+
+    if (realm, authuri) == self.__easyhg_last:
+        # If we are called again just after identical previous
+        # request, then the previously returned auth must have been
+        # wrong. So we note this to force password prompt (and avoid
+        # reusing bad password indefinitely). Thanks to
+        # mercurial_keyring (Marcin Kasperski) for this logic
+        repeat = True
+
+    if user and passwd and not repeat:
+        return orig_find(self, realm, authuri)
+
+    dialog = EasyHgAuthDialog(self.ui, authuri, user, passwd)
+
+    (user, passwd) = dialog.ask(repeat)
+
+    self.add_password(realm, authuri, user, passwd)
+    self.__easyhg_last = (realm, authuri)
+    return (user, passwd)
+
+
--- a/easyhg.qrc	Tue Mar 15 12:36:26 2011 +0000
+++ b/easyhg.qrc	Thu Dec 06 13:54:34 2018 +0000
@@ -22,7 +22,34 @@
         <file>images/hdd_unmount.png</file>
         <file>images/hdd_unmount-64.png</file>
         <file>images/fileopen.png</file>
+        <file>images/star.png</file>
         <file>images/easyhg-icon.png</file>
+        <file>images/home.png</file>
+        <file>images/back.png</file>
+        <file>images/forward.png</file>
+        <file>images/cancel-small.png</file>
+	<file>help/topics.html</file>
+	<file>help/help.css</file>
+	<file>help/a-04.html</file>
+	<file>help/a-10.html</file>
+	<file>help/a-11.html</file>
+	<file>help/a-12.html</file>
+	<file>help/a-13.html</file>
+	<file>help/a-20.html</file>
+	<file>help/a-21.html</file>
+	<file>help/a-22.html</file>
+	<file>help/a-23.html</file>
+	<file>help/a-30.html</file>
+	<file>help/a-31.html</file>
+	<file>help/a-32.html</file>
+	<file>help/a-33.html</file>
+	<file>help/a-34.html</file>
+	<file>help/a-35.html</file>
+	<file>help/a-40.html</file>
+	<file>help/images/openremote50.png</file>
+	<file>help/images/openfolder50.png</file>
+	<file>help/images/openlocal50.png</file>
+	<file>help/images/merge50.png</file>
 	<file>easyhg.py</file>
 	<file>easyhg_en.qm</file>
     </qresource>
--- a/easyhg.rc	Tue Mar 15 12:36:26 2011 +0000
+++ b/easyhg.rc	Thu Dec 06 13:54:34 2018 +0000
@@ -1,1 +1,1 @@
-IDI_ICON1 ICON DISCARDABLE "easyhg-icon.ico"
+IDI_ICON1 ICON DISCARDABLE "easyhg.ico"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/easyhg.wxs	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,389 @@
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+
+  <Product
+      Name="EasyMercurial" 
+      Id="*"
+      Language="1033"
+      Codepage="1252" 
+      Version="1.3.0" 
+      UpgradeCode="B82DFDA9-B9DE-49BC-93E5-0B96F9DEB04B"
+      Manufacturer="Queen Mary, University of London">
+    
+    <Package
+        Id="*"
+        Keywords="Installer"
+        Description="EasyMercurial Installer" 
+        Comments="Copyright (c) 2012 Queen Mary, University of London and others." 
+        Manufacturer="Queen Mary, University of London" 
+        InstallerVersion="100" 
+        Languages="1033" 
+        Compressed="yes" 
+        SummaryCodepage="1252"/>
+
+    <MajorUpgrade DowngradeErrorMessage="A later version of EasyMercurial is already installed. Setup will now exit."/>
+
+    <Media Id="1" Cabinet="easyhg.cab" EmbedCab="yes" DiskPrompt="CD-ROM #1"/>
+    <Property Id="DiskPrompt" Value="EasyMercurial Installation [1]"/>
+
+    <Directory Id="TARGETDIR" Name="SourceDir">
+      <Directory Id="ProgramFilesFolder" Name="PFiles">
+        <Directory Id="INSTALLDIR" Name="EasyMercurial">
+            
+            <Component
+                Id="MainExecutable"
+                Guid="DC128BA9-25A8-431E-8A88-7E0445E850B9">
+
+              <File
+                  Id="EasyHg"
+                  Name="EasyMercurial.exe"
+                  DiskId="1"
+                  Source="release\EasyMercurial.exe"
+                  KeyPath="yes">
+
+                <Shortcut
+                    Id="EasyHgStartEntry"
+                    Directory="ProgramMenuDir"
+                    Name="EasyMercurial"
+                    WorkingDirectory="INSTALLDIR"
+                    Icon="easyhg.ico"
+                    IconIndex="0"
+                    Advertise="yes"/>
+
+                <Shortcut
+                    Id="EasyHgDesktop"
+                    Directory="DesktopFolder"
+                    Name="EasyMercurial"
+                    WorkingDirectory="INSTALLDIR"
+                    Icon="easyhg.ico"
+                    IconIndex="0"
+                    Advertise="yes"/>
+              </File>
+
+              <File
+                  Id="COPYING"
+                  Name="COPYING"
+                  DiskId="1"
+                  Source="COPYING"/>
+
+              <File
+                  Id="easyhg.ico"
+                  Name="easyhg.ico"
+                  DiskId="1"
+                  Source="easyhg.ico"/>
+            </Component>
+
+            <Component
+                Id="HelperScripts"
+                Guid="971EA788-347E-4CDE-9899-69EEBE117BDD">
+
+              <File
+                  Id="mergebat"
+                  Name="easyhg-merge.bat"
+                  DiskId="1"
+                  Source="easyhg-merge.bat"
+                  KeyPath="yes"/>
+            </Component>		  
+
+            <Component
+                Id="VCLIBS"
+                Guid="2566C8F0-A2AD-40E9-A356-94E887CC1222">
+              <File
+                  Id="MSVCP100"
+                  Name="MSVCP100.dll"
+                  DiskId="1"
+                  Source="c:\windows\system32\MSVCP100.DLL"
+                  KeyPath="yes"/>
+              <File
+                  Id="MSVCR100"
+                  Name="MSVCR100.dll"
+                  DiskId="1"
+                  Source="c:\windows\system32\MSVCR100.DLL"/>
+            </Component>
+
+            <Component
+                Id="Qt4"
+                Guid="04F730AC-B361-45A2-B1CC-9CEB55256117">
+              <File
+                  Id="QtCore"
+                  Name="QtCore4.dll"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\QtCore4.dll"
+		  KeyPath="yes"/>
+              <File
+                  Id="QtGui"
+                  Name="QtGui4.dll"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\QtGui4.dll"/>
+              <File
+                  Id="QtNetwork"
+                  Name="QtNetwork4.dll"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\QtNetwork4.dll"/>
+            </Component>
+
+            <Component
+                Id="hg"
+                Guid="5BBC4080-2EDF-4616-A8CB-F96B9C942C1A">
+
+              <File
+                  Id="hg"
+                  Name="hg.exe"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\hg.exe"
+                  KeyPath="yes"/>
+              <File
+                  Id="pydll"
+                  Name="python27.dll"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\python27.dll"/>
+              <File
+                  Id="hglib"
+                  Name="library.zip"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\library.zip"/>
+              <File
+                  Id="plink"
+                  Name="TortoisePlink.exe"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\TortoisePlink.exe"/>
+              <File
+                  Id="ctypes"
+                  Name="_ctypes.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\_ctypes.pyd"/>
+              <File
+                  Id="ctypestest"
+                  Name="_ctypes_test.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\_ctypes_test.pyd"/>
+              <File
+                  Id="elementtree"
+                  Name="_elementtree.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\_elementtree.pyd"/>
+              <File
+                  Id="hashlib"
+                  Name="_hashlib.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\_hashlib.pyd"/>
+              <File
+                  Id="socket"
+                  Name="_socket.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\_socket.pyd"/>
+              <File
+                  Id="ssl"
+                  Name="_ssl.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\_ssl.pyd"/>
+              <File
+                  Id="winsysloader"
+                  Name="_win32sysloader.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\_win32sysloader.pyd"/>
+              <File
+                  Id="bz"
+                  Name="bz2.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\bz2.pyd"/>
+              <File
+                  Id="dulwichobjects"
+                  Name="dulwich._objects.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\dulwich._objects.pyd"/>
+              <File
+                  Id="dulwichpack"
+                  Name="dulwich._pack.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\dulwich._pack.pyd"/>
+              <File
+                  Id="libsvncore"
+                  Name="libsvn._core.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\libsvn._core.pyd"/>
+              <File
+                  Id="libsvnclient"
+                  Name="libsvn._client.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\libsvn._client.pyd"/>
+              <File
+                  Id="libsvndelta"
+                  Name="libsvn._delta.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\libsvn._delta.pyd"/>
+              <File
+                  Id="libsvnra"
+                  Name="libsvn._ra.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\libsvn._ra.pyd"/>
+              <File
+                  Id="libsvnwc"
+                  Name="libsvn._wc.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\libsvn._wc.pyd"/>
+              <File
+                  Id="mercurialbase"
+                  Name="mercurial.base85.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\mercurial.base85.pyd"/>
+              <File
+                  Id="mercurialbdiff"
+                  Name="mercurial.bdiff.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\mercurial.bdiff.pyd"/>
+              <File
+                  Id="mercurialdiffhelpers"
+                  Name="mercurial.diffhelpers.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\mercurial.diffhelpers.pyd"/>
+              <File
+                  Id="mercurialmpatch"
+                  Name="mercurial.mpatch.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\mercurial.mpatch.pyd"/>
+              <File
+                  Id="mercurialosutil"
+                  Name="mercurial.osutil.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\mercurial.osutil.pyd"/>
+              <File
+                  Id="mercurialparsers"
+                  Name="mercurial.parsers.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\mercurial.parsers.pyd"/>
+              <File
+                  Id="pyexpat"
+                  Name="pyexpat.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\pyexpat.pyd"/>
+              <File
+                  Id="unicodedata"
+                  Name="unicodedata.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\unicodedata.pyd"/>
+              <File
+                  Id="QtCorepy"
+                  Name="PyQt4.QtCore.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\PyQt4.QtCore.pyd" />
+              <File
+                  Id="QtGuipy"
+                  Name="PyQt4.QtGui.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\PyQt4.QtGui.pyd" />
+            </Component>		  
+
+            <Component
+                Id="sip"
+                Guid="DCCE0979-A0A6-475D-B6CD-3173740593F9">
+              <File
+                  Id="sip"
+                  Name="sip.pyd"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\sip.pyd" />
+            </Component>
+
+            <Component
+                Id="kdiff3"
+                Guid="6BA0CB20-F426-42AB-A95A-615A4FF87752">
+              <File
+                  Id="kdiff3"
+                  Name="kdiff3.exe"
+                  DiskId="1"
+                  Source="d:\easyhg-bundle-dependencies\kdiff3.exe"
+                  KeyPath="yes"/>
+            </Component>		  
+
+	    <Directory Id="Crypto" Name="Crypto">
+	      <Directory Id="Cipher" Name="Cipher">
+		<Component
+		    Id="cryptocipher"
+		    Guid="FB7C7C7E-FC62-4024-9E7E-47034B416286">
+		  <File
+		      Id="cipherinit"
+		      Name="__init__.py"
+		      DiskId="1"
+		      Source="d:\easyhg-bundle-dependencies\Crypto\Cipher\__init__.py" />
+		  <File
+		      Id="cipheraesd"
+		      Name="_AES.pyd"
+		      DiskId="1"
+		      Source="d:\easyhg-bundle-dependencies\Crypto\Cipher\_AES.pyd" />
+		  <File
+		      Id="cipheraes"
+		      Name="AES.py"
+		      DiskId="1"
+		      Source="d:\easyhg-bundle-dependencies\Crypto\Cipher\AES.py" />
+		  <File
+		      Id="cipherblockalgo"
+		      Name="blockalgo.py"
+		      DiskId="1"
+		      Source="d:\easyhg-bundle-dependencies\Crypto\Cipher\blockalgo.py" />
+		</Component>
+	      </Directory>
+	      <Directory Id="Util" Name="Util">
+		
+		<Component
+		    Id="cryptoutil"
+		    Guid="9B5FB64F-B5B3-4E36-A4E6-1EC648470557">
+		  <File
+		      Id="utilinit"
+		      Name="__init__.py"
+		      DiskId="1"
+		      Source="d:\easyhg-bundle-dependencies\Crypto\Util\__init__.py" />
+		  <File
+		      Id="utilpy3compat"
+		      Name="py3compat.py"
+		      DiskId="1"
+		      Source="d:\easyhg-bundle-dependencies\Crypto\Util\py3compat.py" />
+		</Component>
+	      </Directory>
+	      
+	      <Component
+		  Id="crypto"
+		  Guid="AB5D6864-6D47-4C51-A132-5E0EC5BE1EF8">
+		<File
+		    Id="cryptoinit"
+		    Name="__init__.py"
+		    DiskId="1"
+		    Source="d:\easyhg-bundle-dependencies\Crypto\__init__.py" />
+	      </Component>
+
+	    </Directory>
+
+        </Directory>
+      </Directory>
+
+      <Directory Id="ProgramMenuFolder" Name="Programs">
+        <Directory Id="ProgramMenuDir" Name="EasyMercurial">
+          <Component Id="ProgramMenuDir" Guid="2E8BDEA0-A6E4-4607-854D-E317A23A535B">
+            <RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
+            <RegistryValue Root="HKMU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes"/>
+          </Component>
+        </Directory>
+      </Directory>
+
+      <Directory Id="DesktopFolder" Name="Desktop"/>
+
+    </Directory>
+
+    <Feature Id="Complete" Level="1">
+      <ComponentRef Id="MainExecutable"/>
+      <ComponentRef Id="VCLIBS"/>
+      <ComponentRef Id="HelperScripts"/>
+      <ComponentRef Id="Qt4"/>
+      <ComponentRef Id="hg"/>
+      <ComponentRef Id="sip"/>
+      <ComponentRef Id="crypto"/>
+      <ComponentRef Id="cryptocipher"/>
+      <ComponentRef Id="cryptoutil"/>
+      <ComponentRef Id="kdiff3"/>
+      <ComponentRef Id="ProgramMenuDir"/>
+    </Feature>
+
+    <Icon
+        Id="easyhg.ico"
+        SourceFile="easyhg.ico"/>
+
+  </Product>
+</Wix>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/easyhg1.py	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+#
+#    EasyMercurial
+#
+#    Based on hgExplorer by Jari Korhonen
+#    Copyright (c) 2010 Jari Korhonen
+#    Copyright (c) 2010 Chris Cannam
+#    Copyright (c) 2010 Queen Mary, University of London
+#    
+#    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.
+
+import sys
+from mercurial import ui, getpass, util
+from mercurial.i18n import _
+
+# The value assigned here may be modified during installation, by
+# replacing its default value with another one.  We can't compare
+# against its default value, because then the comparison text would
+# get modified as well.  So, compare using prefix only.
+#
+easyhg_import_path = 'NO_EASYHG_IMPORT_PATH'
+if not easyhg_import_path.startswith('NO_'):
+    # We have an installation path: append it twice, once with
+    # the Python version suffixed
+    version_suffix = "Py" + str(sys.version_info[0]) + "." + str(sys.version_info[1]);
+    sys.path.append(easyhg_import_path + "/" + version_suffix)
+    sys.path.append(easyhg_import_path)
+
+# Try to load the PyQt4 module that we need.  If this fails, we should
+# bail out later (in uisetup), because if we bail out now, Mercurial
+# will just continue without us and report success.  The invoking
+# application needs to be able to discover whether the module load
+# succeeded or not, so we need to ensure that Mercurial itself returns
+# failure if it didn't.
+#
+easyhg_pyqt_ok = True
+try:
+    from PyQt4 import QtGui
+except ImportError:
+    easyhg_pyqt_ok = False
+
+easyhg_qtapp = None
+
+def uisetup(ui):
+    if not easyhg_pyqt_ok:
+        raise util.Abort(_('Failed to load PyQt4 module required by easyhg.py'))
+    ui.__class__.prompt = easyhg_prompt
+    ui.__class__.getpass = easyhg_getpass
+    global easyhg_qtapp
+    easyhg_qtapp = QtGui.QApplication([])
+
+def easyhg_prompt(self, msg, default="y"):
+    if not self.interactive():
+        self.write(msg, ' ', default, "\n")
+        return default
+    isusername = False
+    if msg == _('user:'):
+        msg = _('Username for remote repository:')
+        isusername = True
+    d = QtGui.QInputDialog()
+    d.setInputMode(QtGui.QInputDialog.TextInput)
+    d.setTextEchoMode(QtGui.QLineEdit.Normal)
+    d.setLabelText(msg)
+    d.setWindowTitle(_('EasyMercurial: Information'))
+    d.show()
+    d.raise_()
+    ok = d.exec_()
+    r = d.textValue()
+    if not ok:
+        if isusername:
+            raise util.Abort(_('username entry cancelled'))
+        else:
+            raise util.Abort(_('information entry cancelled'))
+    if not r:
+        return default
+    return r
+
+def easyhg_getpass(self, prompt=None, default=None):
+    if not self.interactive():
+        return default
+    if not prompt or prompt == _('password:'):
+        prompt = _('Password for remote repository:');
+    d = QtGui.QInputDialog()
+    d.setInputMode(QtGui.QInputDialog.TextInput)
+    d.setTextEchoMode(QtGui.QLineEdit.Password)
+    d.setLabelText(prompt)
+    d.setWindowTitle(_('EasyMercurial: Password'))
+    d.show()
+    d.raise_()
+    ok = d.exec_()
+    r = d.textValue()
+    if not ok:
+        raise util.Abort(_('password entry cancelled'))
+    if not r:
+        return default
+    return r
+
+ 
Binary file easyhg_en.qm has changed
--- a/easyhg_en.ts	Tue Mar 15 12:36:26 2011 +0000
+++ b/easyhg_en.ts	Thu Dec 06 13:54:34 2018 +0000
@@ -2,6 +2,25 @@
 <!DOCTYPE TS>
 <TS version="2.0" language="en_GB">
 <context>
+    <name>AnnotateDialog</name>
+    <message>
+        <source>User</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Revision</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Date</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Content</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
     <name>ChangesetItem</name>
     <message>
         <source>&lt;qt&gt;&lt;b&gt;&amp;nbsp;Revision: &lt;/b&gt;%1&lt;/qt&gt;</source>
@@ -109,10 +128,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>These files are unresolved following an incomplete merge.&lt;br&gt;Select a file and use Merge to try to resolve the merge again.</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Files highlighted &lt;font color=#d40000&gt;in red&lt;/font&gt; have appeared since your most recent commit or update.</source>
         <translation type="unfinished"></translation>
     </message>
@@ -160,6 +175,112 @@
         <source>&lt;qt&gt;You have no uncommitted changes.&lt;/qt&gt;</source>
         <translation type="unfinished"></translation>
     </message>
+    <message>
+        <source>Show annotated version</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Diff to parent</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Commit...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Revert to last committed state</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Rename...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Copy...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Add to version control</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Remove from version control</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Redo merge</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Mark conflict as resolved</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignore...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Edit .hgignore File</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>These files are unresolved following an incomplete merge.&lt;br&gt;Use Merge to try to resolve the merge again.</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>HgIgnoreDialog</name>
+    <message>
+        <source>Ignore files</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>You have asked to ignore the following files:&lt;/p&gt;&lt;p&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message numerus="yes">
+        <source>You have asked to ignore %n file(s).</source>
+        <translation type="unfinished">
+            <numerusform></numerusform>
+        </translation>
+    </message>
+    <message>
+        <source>Ignore these files only</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignore files with these names, in any folder</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignore this file only</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignore files with the same name as this, in any folder</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignore the whole folder &quot;%1&quot;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignore all files with these extensions:
+%1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignore all files with the extension &quot;%1&quot;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&lt;p&gt;Please choose whether to:&lt;/p&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignore</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>HgRunner</name>
@@ -215,7 +336,7 @@
     </message>
     <message numerus="yes">
         <source>There are %n change(s) ready to pull</source>
-        <translation type="unfinished">
+        <translation>
             <numerusform>There is %n change ready to pull</numerusform>
             <numerusform>There are %n changes ready to pull</numerusform>
         </translation>
@@ -253,8 +374,8 @@
     </message>
     <message numerus="yes">
         <source>You are about to &lt;b&gt;revert&lt;/b&gt; %n file(s).&lt;br&gt;&lt;br&gt;This will &lt;b&gt;throw away any changes&lt;/b&gt; that you have made to these files but have not committed.</source>
-        <translation type="unfinished">
-            <numerusform>You are about to &lt;b&gt;revert&lt;/b&gt; %n file.&lt;br&gt;&lt;br&gt;This will &lt;b&gt;throw away any changes&lt;/b&gt; that you have made to these files but have not committed.</numerusform>
+        <translation>
+            <numerusform>You are about to &lt;b&gt;revert&lt;/b&gt; %n file.&lt;br&gt;&lt;br&gt;This will &lt;b&gt;throw away any changes&lt;/b&gt; that you have made to this file but have not committed.</numerusform>
             <numerusform>You are about to &lt;b&gt;revert&lt;/b&gt; %n files.&lt;br&gt;&lt;br&gt;This will &lt;b&gt;throw away any changes&lt;/b&gt; that you have made to these files but have not committed.</numerusform>
         </translation>
     </message>
@@ -363,10 +484,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>&lt;qt&gt;&lt;b&gt;Initialise a repository here?&lt;/b&gt;&lt;br&gt;&lt;br&gt;You asked to open &quot;%1&quot;.&lt;br&gt;This folder does not contain a Mercurial repository.&lt;br&gt;&lt;br&gt;Would you like to initialise a repository here?&lt;/qt&gt;</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>&lt;qt&gt;&lt;b&gt;Initialise a new repository?&lt;/b&gt;&lt;br&gt;&lt;br&gt;You asked to open &quot;%1&quot;.&lt;br&gt;This folder does not yet exist.&lt;br&gt;&lt;br&gt;Would you like to create the folder and initialise a new empty repository in it?&lt;/qt&gt;</source>
         <translation type="unfinished"></translation>
     </message>
@@ -378,17 +495,9 @@
         <source>&lt;qt&gt;&lt;b&gt;Open existing repository?&lt;/b&gt;&lt;br&gt;&lt;br&gt;You asked to initialise a new repository at &quot;%1&quot;.&lt;br&gt;This folder already contains a repository.  Would you like to open it?&lt;/qt&gt;</source>
         <translation type="unfinished"></translation>
     </message>
-    <message>
-        <source>Ok</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Mercurial command did not return any output.</source>
-        <translation type="unfinished"></translation>
-    </message>
     <message numerus="yes">
         <source>Pushed %n changeset(s)</source>
-        <translation type="unfinished">
+        <translation>
             <numerusform>Pushed %n changeset</numerusform>
             <numerusform>Pushed %n changesets</numerusform>
         </translation>
@@ -403,7 +512,7 @@
     </message>
     <message numerus="yes">
         <source>Pulled %n changeset(s)</source>
-        <translation type="unfinished">
+        <translation>
             <numerusform>Pulled %n changeset</numerusform>
             <numerusform>Pulled %n changesets</numerusform>
         </translation>
@@ -454,7 +563,7 @@
     </message>
     <message numerus="yes">
         <source>You are about to commit %n file(s) to %1.</source>
-        <translation type="unfinished">
+        <translation type="obsolete">
             <numerusform>You are about to commit %n file to %1.</numerusform>
             <numerusform>You are about to commit %n files to %1.</numerusform>
         </translation>
@@ -485,7 +594,7 @@
     </message>
     <message numerus="yes">
         <source>At one of %n heads of %1</source>
-        <translation type="unfinished">
+        <translation>
             <numerusform>At one of %n heads of %1</numerusform>
             <numerusform>At one of %n heads of %1</numerusform>
         </translation>
@@ -495,22 +604,10 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Open...</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Settings...</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>View and change application settings</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Refresh</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Preview</source>
         <translation type="unfinished"></translation>
     </message>
@@ -527,10 +624,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Revert</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Add</source>
         <translation type="unfinished"></translation>
     </message>
@@ -539,34 +632,10 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Commit</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Annotate</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Show line-by-line version information for selected file</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Serve local repository via http for workgroup access</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>File</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Advanced</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Help</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Ready</source>
         <translation type="unfinished"></translation>
     </message>
@@ -579,10 +648,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>You are about to commit the following files to %1:</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>New Branch</source>
         <translation type="unfinished"></translation>
     </message>
@@ -591,14 +656,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Start Branch</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Add Tag</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; you are reverting only the files you have selected, not all of the files that have been changed!</source>
         <translation type="unfinished"></translation>
     </message>
@@ -627,10 +684,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>&lt;qt&gt;&lt;p&gt;You are about to push your changes to the remote repository at &lt;code&gt;%1&lt;/code&gt;.&lt;/p&gt;&lt;/qt&gt;</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Serve</source>
         <translation type="unfinished"></translation>
     </message>
@@ -639,18 +692,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>&lt;p&gt;Press Close to stop the server and return.&lt;/p&gt;</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Change Remote Location</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>&lt;qt&gt;&lt;big&gt;Change the remote location&lt;/big&gt;&lt;/qt&gt;</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Provide a new URL to use for push and pull actions from the current local repository.</source>
         <translation type="unfinished"></translation>
     </message>
@@ -695,10 +736,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>The new changes will be highlighted in the history.&lt;br&gt;Use Update to bring these changes into your working copy.</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Your local repository already contains all changes found in the remote repository.</source>
         <translation type="unfinished"></translation>
     </message>
@@ -727,10 +764,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>The Mercurial program either could not be found or failed to run.&lt;br&gt;Check that the Mercurial program path is correct in %1.</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Failed to run Mercurial with extension enabled</source>
         <translation type="unfinished"></translation>
     </message>
@@ -747,10 +780,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Clone successful</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>The remote repository was successfully cloned to the local folder &lt;code&gt;%1&lt;/code&gt;.</source>
         <translation type="unfinished"></translation>
     </message>
@@ -799,22 +828,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Change Remote Location...</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Change the default remote repository for pull and push actions</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Quit</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Quit EasyMercurial</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Refresh the window to show the current state of the working folder</source>
         <translation type="unfinished"></translation>
     </message>
@@ -839,14 +852,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Mark the selected file(s) to be added on the next commit</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>Mark the selected file(s) to be removed from version control on the next commit</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Update the working folder to the head of the current repository branch</source>
         <translation type="unfinished"></translation>
     </message>
@@ -859,27 +864,348 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Edit .hgignore File</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>Edit the .hgignore file, containing the names of files that should be ignored by Mercurial</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>Serve via HTTP</source>
+        <source>&lt;qt&gt;&lt;h2&gt;EasyMercurial v%1&lt;/h2&gt;&lt;font size=-1&gt;&lt;p&gt;EasyMercurial is a simple user interface for the Mercurial&lt;/a&gt; version control system.&lt;/p&gt;&lt;h4&gt;Credits and Copyright&lt;/h4&gt;&lt;p&gt;Development carried out by Chris Cannam for SoundSoftware.ac.uk at the Centre for Digital Music, Queen Mary, University of London.&lt;/p&gt;&lt;p&gt;EasyMercurial is based on HgExplorer by Jari Korhonen, with thanks.&lt;/p&gt;&lt;p style=&quot;margin-left: 2em;&quot;&gt;Copyright &amp;copy; 2012 Queen Mary, University of London.&lt;br&gt;Copyright &amp;copy; 2010 Jari Korhonen.&lt;br&gt;Copyright &amp;copy; 2012 Chris Cannam.&lt;/p&gt;&lt;p style=&quot;margin-left: 2em;&quot;&gt;This program requires Mercurial, by Matt Mackall and others.&lt;br&gt;This program uses Qt by Nokia.&lt;br&gt;This program uses Nuvola icons by David Vignoni.&lt;br&gt;This program may use KDiff3 by Joachim Eibl.&lt;br&gt;This program may use PyQt by River Bank Computing.&lt;br&gt;Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others.&lt;/p&gt;&lt;h4&gt;License&lt;/h4&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/font&gt;</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>&lt;qt&gt;&lt;h2&gt;EasyMercurial v%1&lt;/h2&gt;&lt;font size=-1&gt;&lt;p&gt;EasyMercurial is a simple user interface for the Mercurial&lt;/a&gt; version control system.&lt;/p&gt;&lt;h4&gt;Credits and Copyright&lt;/h4&gt;&lt;p&gt;Development carried out by Chris Cannam for SoundSoftware.ac.uk at the Centre for Digital Music, Queen Mary, University of London.&lt;/p&gt;&lt;p&gt;EasyMercurial is based on HgExplorer by Jari Korhonen, with thanks.&lt;/p&gt;&lt;p style=&quot;margin-left: 2em;&quot;&gt;Copyright &amp;copy; 2011 Queen Mary, University of London.&lt;br&gt;Copyright &amp;copy; 2010 Jari Korhonen.&lt;br&gt;Copyright &amp;copy; 2011 Chris Cannam.&lt;/p&gt;&lt;p style=&quot;margin-left: 2em;&quot;&gt;This program requires Mercurial, by Matt Mackall and others.&lt;br&gt;This program uses Qt by Nokia.&lt;br&gt;This program uses Nuvola icons by David Vignoni.&lt;br&gt;This program may use KDiff3 by Joachim Eibl.&lt;br&gt;This program may use PyQt by River Bank Computing.&lt;br&gt;Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others.&lt;/p&gt;&lt;h4&gt;License&lt;/h4&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/font&gt;</source>
+        <source>You are about to commit changes to the following files in %1:</source>
         <translation type="unfinished"></translation>
     </message>
     <message numerus="yes">
-        <source>Running temporary server at %n address(es):</source>
-        <translation type="unfinished">
-            <numerusform></numerusform>
+        <source>You are about to commit changes to %n file(s) in %1.</source>
+        <translation>
+            <numerusform>You are about to commit changes to %n file in %1.</numerusform>
+            <numerusform>You are about to commit changes to %n files in %1.</numerusform>
         </translation>
     </message>
+    <message>
+        <source>Co&amp;mmit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Start &amp;Branch</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Add &amp;Tag</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&lt;qt&gt;&lt;h3&gt;Ignored File Patterns&lt;/h3&gt;&lt;/qt&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Write failed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Failed to open file %1 for writing</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ignored files</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>The following lines have been added to the .hgignore file for this working copy:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Re&amp;vert</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Rename</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Rename &lt;code&gt;%1&lt;/code&gt; to:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Re&amp;name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Copy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Copy &lt;code&gt;%1&lt;/code&gt; to:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Co&amp;py</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Pull</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; You have uncommitted changes.  If you want to push these changes to the remote repository, you need to commit them first.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&lt;qt&gt;&lt;p&gt;You are about to push your commits to the remote repository at &lt;code&gt;%1&lt;/code&gt;.&lt;/p&gt;%2&lt;/qt&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Push</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Sharing Repository</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Your local repository is now being made temporarily available via HTTP for workgroup access.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Users who have network access to your computer can now clone your repository, by using one of the following URLs as a remote location:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Users who have network access to your computer can now clone your repository, by using the following URL as a remote location:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&lt;p&gt;Press Close to terminate this server, end remote access, and return.&lt;/p&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Share Repository</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Set Remote Location</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&lt;qt&gt;&lt;big&gt;Set the remote location&lt;/big&gt;&lt;/qt&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Provide a URL to use for push and pull actions from the current local repository.&lt;br&gt;This will be the default for subsequent pushes and pulls.&lt;br&gt;You can change it using &amp;ldquo;Set Remote Location&amp;rdquo; on the File menu.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&lt;qt&gt;&lt;b&gt;Initialise a repository here?&lt;/b&gt;&lt;br&gt;&lt;br&gt;You asked to open &quot;%1&quot;.&lt;br&gt;This folder is not a Mercurial working copy.&lt;br&gt;&lt;br&gt;Would you like to initialise a repository here?&lt;/qt&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>New changes will be highlighted in yellow in the history.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Your local repository could not be pushed to the remote repository.&lt;br&gt;&lt;br&gt;You have an uncommitted merge in your local folder.  You probably need to commit it before you push.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Authorization failed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>You may have entered an incorrect user name or password, or the remote URL may be wrong.&lt;br&gt;&lt;br&gt;Or you may lack the necessary permissions on the remote repository.&lt;br&gt;&lt;br&gt;Check with the administrator of your remote repository if necessary.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>The Mercurial program either could not be found or failed to run.&lt;br&gt;&lt;br&gt;Check that the Mercurial program path is correct in %1.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Merge failed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Some files were not merged successfully.&lt;p&gt;You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file&apos;s right-button menu.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>A Mercurial command failed to run correctly.  This may indicate an installation problem or some other problem with EasyMercurial.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Newer Mercurial version required</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>To use EasyMercurial, you should have at least Mercurial v1.7 installed.&lt;br&gt;&lt;br&gt;The version found on this system (v%1.%2) does not support all of the features required by EasyMercurial.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Open successful</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>No recent local repositories</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Open...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+O</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Set Remote &amp;Location...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Set or change the default remote repository for pull and push actions</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Settings...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>E&amp;xit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Quit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Exit EasyMercurial</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Refresh</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+R</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Pre&amp;view Incoming Changes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Pu&amp;ll from Remote Repository</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+L</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Pus&amp;h to Remote Repository</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+H</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Diff</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+D</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Add Files</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Mark the selected files to be added on the next commit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Remove Files</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Del</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Mark the selected files to be removed from version control on the next commit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Ignore Files...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Add the selected filenames to the ignored list, of files that should never be tracked in this repository</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Edit Ignored List</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Update to Branch Head</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+U</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Commit...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+Return</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Merge</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+M</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Serve local repository temporarily via HTTP for workgroup access</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;File</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Open Re&amp;cent</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Work</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Remote</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&amp;Help</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Remote</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Work</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>MoreInformationDialog</name>
@@ -922,6 +1248,18 @@
         <source>&amp;Folder:</source>
         <translation type="unfinished"></translation>
     </message>
+    <message>
+        <source>Documents</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>My Documents</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Desktop</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>QApplication</name>
@@ -1039,10 +1377,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>External text editor:</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>EasyHg Mercurial extension:</source>
         <translation type="unfinished"></translation>
     </message>
@@ -1067,10 +1401,6 @@
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <source>External text editor</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
         <source>EasyHg Mercurial extension</source>
         <translation type="unfinished"></translation>
     </message>
@@ -1082,6 +1412,14 @@
         <source>&lt;qt&gt;&lt;b&gt;Restore default settings?&lt;/b&gt;&lt;br&gt;&lt;br&gt;Are you sure you want to reset all settings to their default values?</source>
         <translation type="unfinished"></translation>
     </message>
+    <message>
+        <source>SSH program (for ssh URLs):</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>SSH program</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>StartupDialog</name>
@@ -1148,6 +1486,18 @@
         <source>Cancel new branch</source>
         <translation type="unfinished"></translation>
     </message>
+    <message>
+        <source>&lt;qt&gt;&lt;b&gt;&amp;nbsp;Uncommitted merge&lt;/b&gt;&lt;/qt&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Uncommitted merge</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>merge</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>WorkStatusWidget</name>
--- a/filestates.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,220 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 "filestates.h"
-
-#include "debug.h"
-
-#include <QMap>
-
-FileStates::FileStates()
-{
-}
-
-void FileStates::clearBuckets()
-{
-    m_clean.clear();
-    m_modified.clear();
-    m_added.clear();
-    m_removed.clear();
-    m_missing.clear();
-    m_inConflict.clear();
-    m_unknown.clear();
-    m_ignored.clear();
-}
-
-FileStates::State FileStates::charToState(QChar c, bool *ok)
-{
-    // Note that InConflict does not correspond to a stat char -- it's
-    // reported separately, by resolve --list, which shows U for
-    // Unresolved -- stat reports files in conflict as M, which means
-    // they will appear in more than one bin if we handle them
-    // naively.  'u' is also used by stat as the command option for
-    // Unknown, but the stat output uses ? for these so there's no
-    // ambiguity in parsing.
-
-    if (ok) *ok = true;
-    if (c == 'M') return Modified;
-    if (c == 'A') return Added;
-    if (c == 'R') return Removed;
-    if (c == '!') return Missing;
-    if (c == 'U') return InConflict;
-    if (c == '?') return Unknown;
-    if (c == 'C') return Clean;
-    if (c == 'I') return Ignored;
-    if (ok) *ok = false;
-    return Unknown;
-}
-
-QStringList *FileStates::stateToBucket(State s)
-{
-    switch (s) {
-    case Clean: return &m_clean;
-    case Modified: return &m_modified;
-    case Added: return &m_added;
-    case Unknown: return &m_unknown;
-    case Removed: return &m_removed;
-    case Missing: return &m_missing;
-    case InConflict: return &m_inConflict;
-    case Ignored: return &m_ignored;
-
-    default: return &m_clean;
-    }
-}
-
-void FileStates::parseStates(QString text)
-{
-    text.replace("\r\n", "\n");
-
-    clearBuckets();
-    m_stateMap.clear();
-
-    QStringList lines = text.split("\n", QString::SkipEmptyParts);
-
-    foreach (QString line, lines) {
-
-        if (line.length() < 3 || line[1] != ' ') {
-            continue;
-        }
-
-        QChar c = line[0];
-        bool ok = false;
-        State s = charToState(c, &ok);
-        if (!ok) continue;
-
-        QString file = line.right(line.length() - 2);
-        m_stateMap[file] = s;
-    }
-
-    foreach (QString file, m_stateMap.keys()) {
-        QStringList *bucket = stateToBucket(m_stateMap[file]);
-        if (bucket) bucket->push_back(file);
-    }
-
-    DEBUG << "FileStates: "
-          << m_modified.size() << " modified, " << m_added.size()
-          << " added, " << m_removed.size() << " removed, " << m_missing.size()
-          << " missing, " << m_inConflict.size() << " in conflict, "
-          << m_unknown.size() << " unknown" << endl;
-}
-
-QStringList FileStates::filesInState(State s) const
-{
-    QStringList *sl = const_cast<FileStates *>(this)->stateToBucket(s);
-    if (sl) return *sl;
-    else return QStringList();
-}
-
-bool FileStates::isInState(QString file, State s) const
-{
-    return filesInState(s).contains(file);
-}
-
-FileStates::State FileStates::stateOf(QString file) const
-{
-    if (m_stateMap.contains(file)) {
-        return m_stateMap[file];
-    }
-    DEBUG << "FileStates: WARNING: getStateOfFile: file "
-            << file << " is unknown to us: returning Unknown state, "
-            << "but unknown to us is not supposed to be the same "
-            << "thing as unknown state..."
-            << endl;
-    return Unknown;
-}
-
-FileStates::Activities FileStates::activitiesSupportedBy(State s)
-{
-    Activities a;
-
-    switch (s) {
-
-    case Modified:
-        a << Annotate << Diff << Commit << Revert << Remove;
-        break;
-
-    case Added:
-        a << Commit << Revert << Remove;
-        break;
-        
-    case Removed:
-        a << Commit << Revert << Add;
-        break;
-
-    case InConflict:
-        a << Annotate << Diff << RedoMerge << MarkResolved << Revert;
-        break;
-
-    case Missing:
-        a << Revert << Remove;
-        break;
-        
-    case Unknown:
-        a << Add << Ignore;
-        break;
-
-    case Clean:
-        a << Annotate << Remove;
-        break;
-
-    case Ignored:
-        a << UnIgnore;
-        break;
-    }
-
-    return a;
-}
-
-bool FileStates::supportsActivity(State s, Activity a)
-{
-    return activitiesSupportedBy(s).contains(a);
-}
-
-int FileStates::activityGroup(Activity a)
-{
-    switch (a) {
-    case Annotate: case Diff: return 0;
-    case Commit: case Revert: return 1;
-    case Add: case Remove: return 2;
-    case RedoMerge: case MarkResolved: return 3;
-    case Ignore: case UnIgnore: return 4;
-    }
-    return 0;
-}
-
-bool FileStates::supportsActivity(QString file, Activity a) const
-{
-    return supportsActivity(stateOf(file), a);
-}
-
-QStringList FileStates::filesSupportingActivity(Activity a) const
-{
-    QStringList f;
-    for (int i = int(FirstState); i <= int(LastState); ++i) {
-        State s = (State)i;
-        if (supportsActivity(s, a)) {
-            f << filesInState(s);
-        }
-    }
-    return f;
-}
-
-FileStates::Activities FileStates::activitiesSupportedBy(QString file) const
-{
-    return activitiesSupportedBy(stateOf(file));
-}
-
--- a/filestates.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 FILESTATES_H
-#define FILESTATES_H
-
-#include <QStringList>
-#include <QMap>
-#include <QString>
-
-class FileStates
-{
-public:
-    FileStates();
-
-    enum State {
-
-        // These are in the order in which they want to be listed in
-        // the interface
-
-        Modified,
-        Added,
-        Removed,
-        InConflict,
-        Missing,
-        Unknown,
-        Clean,
-        Ignored,
-
-        FirstState = Modified,
-        LastState = Ignored
-    };
-
-    void parseStates(QString text);
-
-    bool isInState(QString file, State s) const;
-    QStringList filesInState(State s) const;
-    State stateOf(QString file) const;
-
-/*!!! -- to remove: */
-    QStringList modified() const { return m_modified; }
-    QStringList added() const { return m_added; }
-    QStringList unknown() const { return m_unknown; }
-    QStringList removed() const { return m_removed; }
-    QStringList missing() const { return m_missing; }
-    QStringList inConflict() const { return m_inConflict; }
-    QStringList clean() const { return m_clean; }
-    QStringList ignored() const { return m_ignored; }
-
-    enum Activity {
-
-        // These are in the order in which they want to be listed in
-        // the context menu
-
-        Diff,
-        Annotate,
-
-        Commit,
-        Revert,
-
-        Add,
-        Remove,
-
-        RedoMerge,
-        MarkResolved,
-
-        Ignore,
-        UnIgnore,
-
-        FirstActivity = Diff,
-        LastActivity = UnIgnore
-    };
-
-    typedef QList<Activity> Activities;
-
-    static bool supportsActivity(State s, Activity a);
-    static Activities activitiesSupportedBy(State s);
-    static int activityGroup(Activity a);
-    
-    bool supportsActivity(QString file, Activity a) const;
-    QStringList filesSupportingActivity(Activity) const;
-    Activities activitiesSupportedBy(QString file) const;
-
-private:
-    QStringList m_modified;
-    QStringList m_added;
-    QStringList m_unknown;
-    QStringList m_removed;
-    QStringList m_missing;
-    QStringList m_inConflict;
-    QStringList m_clean;
-    QStringList m_ignored;
-    QMap<QString, State> m_stateMap;
-
-    void clearBuckets();
-
-    State charToState(QChar, bool * = 0);
-    QStringList *stateToBucket(State);
-};
-
-#endif // FILESTATES_H
--- a/filestatuswidget.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,521 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "filestatuswidget.h"
-#include "debug.h"
-#include "multichoicedialog.h"
-
-#include <QLabel>
-#include <QListWidget>
-#include <QGridLayout>
-#include <QFileInfo>
-#include <QApplication>
-#include <QDateTime>
-#include <QPushButton>
-#include <QToolButton>
-#include <QDir>
-#include <QProcess>
-#include <QCheckBox>
-#include <QSettings>
-#include <QAction>
-
-FileStatusWidget::FileStatusWidget(QWidget *parent) :
-    QWidget(parent),
-    m_dateReference(0)
-{
-    QGridLayout *layout = new QGridLayout;
-    layout->setMargin(10);
-    setLayout(layout);
-
-    int row = 0;
-
-    m_noModificationsLabel = new QLabel;
-    setNoModificationsLabelText();
-    layout->addWidget(m_noModificationsLabel, row, 0);
-    m_noModificationsLabel->hide();
-
-    m_simpleLabels[FileStates::Clean] = tr("Unmodified:");
-    m_simpleLabels[FileStates::Modified] = tr("Modified:");
-    m_simpleLabels[FileStates::Added] = tr("Added:");
-    m_simpleLabels[FileStates::Removed] = tr("Removed:");
-    m_simpleLabels[FileStates::Missing] = tr("Missing:");
-    m_simpleLabels[FileStates::InConflict] = tr("In Conflict:");
-    m_simpleLabels[FileStates::Unknown] = tr("Untracked:");
-    m_simpleLabels[FileStates::Ignored] = tr("Ignored:");
-
-    m_actionLabels[FileStates::Annotate] = tr("Show annotated version");
-    m_actionLabels[FileStates::Diff] = tr("Diff to parent");
-    m_actionLabels[FileStates::Commit] = tr("Commit...");
-    m_actionLabels[FileStates::Revert] = tr("Revert to last committed state");
-    m_actionLabels[FileStates::Add] = tr("Add to version control");
-    m_actionLabels[FileStates::Remove] = tr("Remove from version control");
-    m_actionLabels[FileStates::RedoMerge] = tr("Redo merge");
-    m_actionLabels[FileStates::MarkResolved] = tr("Mark conflict as resolved");
-    m_actionLabels[FileStates::Ignore] = tr("Ignore");
-    m_actionLabels[FileStates::UnIgnore] = tr("Stop ignoring");
-
-    m_descriptions[FileStates::Clean] = tr("You have not changed these files.");
-    m_descriptions[FileStates::Modified] = tr("You have changed these files since you last committed them.");
-    m_descriptions[FileStates::Added] = tr("These files will be added to version control next time you commit them.");
-    m_descriptions[FileStates::Removed] = tr("These files will be removed from version control next time you commit them.<br>"
-                                             "They will not be deleted from the local folder.");
-    m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.<br>"
-                                             "If you intended to delete them, select them and use Remove to tell the version control system about it.<br>"
-                                             "If you deleted them by accident, select them and use Revert to restore their previous contents.");
-    m_descriptions[FileStates::InConflict] = tr("These files are unresolved following an incomplete merge.<br>Select a file and use Merge to try to resolve the merge again.");
-    m_descriptions[FileStates::Unknown] = tr("These files are in your working folder but are not under version control.<br>"
-//                                             "Select a file and use Add to place it under version control or Ignore to remove it from this list.");
-                                             "Select a file and use Add to place it under version control.");
-    m_descriptions[FileStates::Ignored] = tr("These files have names that match entries in the working folder's .hgignore file,<br>"
-                                             "and so will be ignored by the version control system.");
-
-    m_highlightExplanation = tr("Files highlighted <font color=#d40000>in red</font> "
-                                "have appeared since your most recent commit or update.");
-
-    m_boxesParent = new QWidget(this);
-    layout->addWidget(m_boxesParent, ++row, 0);
-
-    QGridLayout *boxesLayout = new QGridLayout;
-    boxesLayout->setMargin(0);
-    m_boxesParent->setLayout(boxesLayout);
-    int boxRow = 0;
-
-    for (int i = int(FileStates::FirstState);
-         i <= int(FileStates::LastState); ++i) {
-
-        FileStates::State s = FileStates::State(i);
-
-        QWidget *box = new QWidget(m_boxesParent);
-        QGridLayout *boxlayout = new QGridLayout;
-        boxlayout->setMargin(0);
-        box->setLayout(boxlayout);
-
-        boxlayout->addItem(new QSpacerItem(3, 3), 0, 0);
-
-        QLabel *label = new QLabel(labelFor(s));
-        label->setWordWrap(true);
-        boxlayout->addWidget(label, 1, 0);
-
-        QListWidget *w = new QListWidget;
-        m_stateListMap[s] = w;
-        w->setSelectionMode(QListWidget::ExtendedSelection);
-        boxlayout->addWidget(w, 2, 0);
-
-        connect(w, SIGNAL(itemSelectionChanged()),
-                this, SLOT(itemSelectionChanged()));
-        connect(w, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
-                this, SLOT(itemDoubleClicked(QListWidgetItem *)));
-
-        FileStates::Activities activities = m_fileStates.activitiesSupportedBy(s);
-        int prevGroup = -1;
-        foreach (FileStates::Activity a, activities) {
-            // Skip activities which are not yet implemented
-            if (a == FileStates::Ignore ||
-                a == FileStates::UnIgnore) {
-                continue;
-            }
-            int group = FileStates::activityGroup(a);
-            if (group != prevGroup && prevGroup != -1) {
-                QAction *sep = new QAction("", w);
-                sep->setSeparator(true);
-                w->insertAction(0, sep);
-            }
-            prevGroup = group;
-            QAction *act = new QAction(m_actionLabels[a], w);
-            act->setProperty("state", s);
-            act->setProperty("activity", a);
-            connect(act, SIGNAL(triggered()), this, SLOT(menuActionActivated()));
-            w->insertAction(0, act);
-        }
-        w->setContextMenuPolicy(Qt::ActionsContextMenu);
-
-        boxlayout->addItem(new QSpacerItem(2, 2), 3, 0);
-
-        boxesLayout->addWidget(box, ++boxRow, 0);
-        m_boxes.push_back(box);
-        box->hide();
-    }
-
-    m_gridlyLayout = false;
-
-    layout->setRowStretch(++row, 20);
-
-    layout->addItem(new QSpacerItem(8, 8), ++row, 0);
-
-    m_showAllFiles = new QCheckBox(tr("Show all files"), this);
-    m_showAllFiles->setEnabled(false);
-    layout->addWidget(m_showAllFiles, ++row, 0, Qt::AlignLeft);
-    connect(m_showAllFiles, SIGNAL(toggled(bool)),
-            this, SIGNAL(showAllChanged(bool)));
-}
-
-FileStatusWidget::~FileStatusWidget()
-{
-    delete m_dateReference;
-}
-
-QString FileStatusWidget::labelFor(FileStates::State s, bool addHighlightExplanation)
-{
-    QSettings settings;
-    settings.beginGroup("Presentation");
-    if (settings.value("showhelpfultext", true).toBool()) {
-        if (addHighlightExplanation) {
-            return QString("<qt><b>%1</b><br>%2<br>%3</qt>")
-                .arg(m_simpleLabels[s])
-                .arg(m_descriptions[s])
-                .arg(m_highlightExplanation);
-        } else {
-            return QString("<qt><b>%1</b><br>%2</qt>")
-                .arg(m_simpleLabels[s])
-                .arg(m_descriptions[s]);
-        }
-    } else {
-        return QString("<qt><b>%1</b></qt>")
-            .arg(m_simpleLabels[s]);
-    }
-    settings.endGroup();
-}
-
-void FileStatusWidget::setNoModificationsLabelText()
-{
-    QSettings settings;
-    settings.beginGroup("Presentation");
-    if (settings.value("showhelpfultext", true).toBool()) {
-        m_noModificationsLabel->setText
-            (tr("<qt>This area will list files in your working folder that you have changed.<br><br>At the moment you have no uncommitted changes.<br><br>To see changes previously made to the repository,<br>switch to the History tab.<br><br>%1</qt>")
-#if defined Q_OS_MAC
-             .arg(tr("To open the working folder in Finder,<br>click on the &ldquo;Local&rdquo; folder path shown above."))
-#elif defined Q_OS_WIN32
-             .arg(tr("To open the working folder in Windows Explorer,<br>click on the &ldquo;Local&rdquo; folder path shown above."))
-#else
-             .arg(tr("To open the working folder in your system file manager,<br>click the &ldquo;Local&rdquo; folder path shown above."))
-#endif
-                );
-    } else {
-        m_noModificationsLabel->setText
-            (tr("<qt>You have no uncommitted changes.</qt>"));
-    }
-}
-
-
-void FileStatusWidget::menuActionActivated()
-{
-    QAction *act = qobject_cast<QAction *>(sender());
-    if (!act) return;
-    
-    FileStates::State state = (FileStates::State)
-        act->property("state").toUInt();
-    FileStates::Activity activity = (FileStates::Activity)
-        act->property("activity").toUInt();
-
-    DEBUG << "menuActionActivated: state = " << state << ", activity = "
-          << activity << endl;
-
-    if (!FileStates::supportsActivity(state, activity)) {
-        std::cerr << "WARNING: FileStatusWidget::menuActionActivated: "
-                  << "Action state " << state << " does not support activity "
-                  << activity << std::endl;
-        return;
-    }
-
-    QStringList files = getSelectedFilesInState(state);
-
-    switch (activity) {
-    case FileStates::Annotate: emit annotateFiles(files); break;
-    case FileStates::Diff: emit diffFiles(files); break;
-    case FileStates::Commit: emit commitFiles(files); break;
-    case FileStates::Revert: emit revertFiles(files); break;
-    case FileStates::Add: emit addFiles(files); break;
-    case FileStates::Remove: emit removeFiles(files); break;
-    case FileStates::RedoMerge: emit redoFileMerges(files); break;
-    case FileStates::MarkResolved: emit markFilesResolved(files); break;
-    case FileStates::Ignore: emit ignoreFiles(files); break;
-    case FileStates::UnIgnore: emit unIgnoreFiles(files); break;
-    }
-}
-
-void FileStatusWidget::itemDoubleClicked(QListWidgetItem *item)
-{
-    QStringList files;
-    files << item->text();
-    emit annotateFiles(files);
-}
-
-void FileStatusWidget::itemSelectionChanged()
-{
-    DEBUG << "FileStatusWidget::itemSelectionChanged" << endl;
-
-    QListWidget *list = qobject_cast<QListWidget *>(sender());
-
-    if (list) {
-        foreach (QListWidget *w, m_stateListMap) {
-            if (w != list) {
-                w->blockSignals(true);
-                w->clearSelection();
-                w->blockSignals(false);
-            }
-        }
-    }
-
-    m_selectedFiles.clear();
-
-    foreach (QListWidget *w, m_stateListMap) {
-        QList<QListWidgetItem *> sel = w->selectedItems();
-        foreach (QListWidgetItem *i, sel) {
-            m_selectedFiles.push_back(i->text());
-            DEBUG << "file " << i->text() << " is selected" << endl;
-        }
-    }
-
-    emit selectionChanged();
-}
-
-void FileStatusWidget::clearSelections()
-{
-    m_selectedFiles.clear();
-    foreach (QListWidget *w, m_stateListMap) {
-        w->clearSelection();
-    }
-}
-
-bool FileStatusWidget::haveChangesToCommit() const
-{
-    return !getAllCommittableFiles().empty();
-}
-
-bool FileStatusWidget::haveSelection() const
-{
-    return !m_selectedFiles.empty();
-}
-
-QStringList FileStatusWidget::getSelectedFilesInState(FileStates::State s) const
-{
-    QStringList files;
-    foreach (QString f, m_selectedFiles) {
-        if (m_fileStates.stateOf(f) == s) files.push_back(f);
-    }
-    return files;
-}    
-
-QStringList FileStatusWidget::getSelectedFilesSupportingActivity(FileStates::Activity a) const
-{
-    QStringList files;
-    foreach (QString f, m_selectedFiles) {
-        if (m_fileStates.supportsActivity(f, a)) files.push_back(f);
-    }
-    return files;
-}    
-
-QStringList FileStatusWidget::getAllCommittableFiles() const
-{
-    return m_fileStates.filesSupportingActivity(FileStates::Commit);
-}
-
-QStringList FileStatusWidget::getAllRevertableFiles() const
-{
-    return m_fileStates.filesSupportingActivity(FileStates::Revert);
-}
-
-QStringList FileStatusWidget::getAllUnresolvedFiles() const
-{
-    return m_fileStates.filesInState(FileStates::InConflict);
-}
-
-QStringList FileStatusWidget::getSelectedAddableFiles() const
-{
-    return getSelectedFilesSupportingActivity(FileStates::Add);
-}
-
-QStringList FileStatusWidget::getSelectedRemovableFiles() const
-{
-    return getSelectedFilesSupportingActivity(FileStates::Remove);
-}
-
-QString
-FileStatusWidget::localPath() const
-{
-    return m_localPath;
-}
-
-void
-FileStatusWidget::setLocalPath(QString p)
-{
-    m_localPath = p;
-    delete m_dateReference;
-    m_dateReference = new QFileInfo(p + "/.hg/dirstate");
-    if (!m_dateReference->exists() ||
-        !m_dateReference->isFile() ||
-        !m_dateReference->isReadable()) {
-        DEBUG << "FileStatusWidget::setLocalPath: date reference file "
-                << m_dateReference->absoluteFilePath()
-                << " does not exist, is not a file, or cannot be read"
-                << endl;
-        delete m_dateReference;
-        m_dateReference = 0;
-        m_showAllFiles->setEnabled(false);
-    } else {
-        m_showAllFiles->setEnabled(true);
-    }
-}
-
-void
-FileStatusWidget::setFileStates(FileStates p)
-{
-    m_fileStates = p;
-    updateWidgets();
-}
-
-void
-FileStatusWidget::updateWidgets()
-{
-    QDateTime lastInteractionTime;
-    if (m_dateReference) {
-        lastInteractionTime = m_dateReference->lastModified();
-        DEBUG << "reference time: " << lastInteractionTime << endl;
-    }
-
-    QSet<QString> selectedFiles;
-    foreach (QString f, m_selectedFiles) selectedFiles.insert(f);
-
-    int visibleCount = 0;
-
-    foreach (FileStates::State s, m_stateListMap.keys()) {
-
-        QListWidget *w = m_stateListMap[s];
-        w->clear();
-        QStringList files = m_fileStates.filesInState(s);
-
-        QStringList highPriority, lowPriority;
-
-        foreach (QString file, files) {
-
-            bool highlighted = false;
-
-            if (s == FileStates::Unknown) {
-                // We want to highlight untracked files that have appeared
-                // since the last interaction with the repo
-                QString fn(m_localPath + "/" + file);
-                DEBUG << "comparing with " << fn << endl;
-                QFileInfo fi(fn);
-                if (fi.exists() && fi.created() > lastInteractionTime) {
-                    DEBUG << "file " << fn << " is newer (" << fi.lastModified()
-                            << ") than reference" << endl;
-                    highlighted = true;
-                }
-            }
-
-            if (highlighted) {
-                highPriority.push_back(file);
-            } else {
-                lowPriority.push_back(file);
-            }
-        }
-
-        foreach (QString file, highPriority) {
-            QListWidgetItem *item = new QListWidgetItem(file);
-            w->addItem(item);
-            item->setForeground(QColor("#d40000"));
-            item->setSelected(selectedFiles.contains(file));
-        }
-
-        foreach (QString file, lowPriority) {
-            QListWidgetItem *item = new QListWidgetItem(file);
-            w->addItem(item);
-            item->setSelected(selectedFiles.contains(file));
-        }
-
-        setLabelFor(w, s, !highPriority.empty());
-
-        if (files.empty()) {
-            w->parentWidget()->hide();
-        } else {
-            w->parentWidget()->show();
-            ++visibleCount;
-        }
-    }
-
-    m_noModificationsLabel->setVisible(visibleCount == 0);
-
-    if (visibleCount > 3) {
-        layoutBoxesGridly(visibleCount);
-    } else {
-        layoutBoxesLinearly();
-    }
-
-    setNoModificationsLabelText();
-}
-
-void FileStatusWidget::layoutBoxesGridly(int visibleCount)
-{
-    if (m_gridlyLayout && m_lastGridlyCount == visibleCount) return;
-
-    delete m_boxesParent->layout();
-    
-    QGridLayout *layout = new QGridLayout;
-    layout->setMargin(0);
-    m_boxesParent->setLayout(layout);
-
-    int row = 0;
-    int col = 0;
-
-    DEBUG << "FileStatusWidget::layoutBoxesGridly: visibleCount = "
-          << visibleCount << endl;
-
-    for (int i = 0; i < m_boxes.size(); ++i) {
-
-        if (!m_boxes[i]->isVisible()) continue;
-
-        if (col == 0 && row >= (visibleCount+1)/2) {
-            layout->addItem(new QSpacerItem(10, 5), 0, 1);
-            col = 2;
-            row = 0;
-        }
-
-        layout->addWidget(m_boxes[i], row, col);
-
-        ++row;
-    }
-
-    m_gridlyLayout = true;
-    m_lastGridlyCount = visibleCount;
-}
-
-void FileStatusWidget::layoutBoxesLinearly()
-{
-    if (!m_gridlyLayout) return;
-
-    delete m_boxesParent->layout();
-    
-    QGridLayout *layout = new QGridLayout;
-    layout->setMargin(0);
-    m_boxesParent->setLayout(layout);
-
-    for (int i = 0; i < m_boxes.size(); ++i) {
-        layout->addWidget(m_boxes[i], i, 0);
-    }
-
-    m_gridlyLayout = false;
-}
-
-void FileStatusWidget::setLabelFor(QWidget *w, FileStates::State s, bool addHighlight)
-{
-    QString text = labelFor(s, addHighlight);
-    QWidget *p = w->parentWidget();
-    QList<QLabel *> ql = p->findChildren<QLabel *>();
-    if (!ql.empty()) ql[0]->setText(text);
-}
-
--- a/filestatuswidget.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 FILESTATUSWIDGET_H
-#define FILESTATUSWIDGET_H
-
-#include "filestates.h"
-
-#include <QWidget>
-#include <QList>
-
-class QLabel;
-class QListWidget;
-class QListWidgetItem;
-class QPushButton;
-class QFileInfo;
-class QCheckBox;
-
-class FileStatusWidget : public QWidget
-{
-    Q_OBJECT
-
-public:
-    FileStatusWidget(QWidget *parent = 0);
-    ~FileStatusWidget();
-
-    QString localPath() const;
-    void setLocalPath(QString p);
-
-    FileStates fileStates() const;
-    void setFileStates(FileStates sp);
-
-    bool haveChangesToCommit() const;
-    bool haveSelection() const;
-
-    QStringList getAllCommittableFiles() const;
-    QStringList getAllRevertableFiles() const;
-    QStringList getAllUnresolvedFiles() const;
-
-    QStringList getSelectedAddableFiles() const;
-    QStringList getSelectedRemovableFiles() const;
-
-signals:
-    void selectionChanged();
-    void showAllChanged(bool);
-
-    void annotateFiles(QStringList);
-    void diffFiles(QStringList);
-    void commitFiles(QStringList);
-    void revertFiles(QStringList);
-    void addFiles(QStringList);
-    void removeFiles(QStringList);
-    void redoFileMerges(QStringList);
-    void markFilesResolved(QStringList);
-    void ignoreFiles(QStringList);
-    void unIgnoreFiles(QStringList);
-
-public slots:
-    void clearSelections();
-    void updateWidgets();
-
-private slots:
-    void menuActionActivated();
-    void itemSelectionChanged();
-    void itemDoubleClicked(QListWidgetItem *);
-
-private:
-    QString m_localPath;
-    QLabel *m_noModificationsLabel;
-
-    QCheckBox *m_showAllFiles;
-    
-    FileStates m_fileStates;
-    QMap<FileStates::State, QString> m_simpleLabels;
-    QMap<FileStates::State, QString> m_descriptions;
-    QMap<FileStates::State, QListWidget *> m_stateListMap;
-    QMap<FileStates::Activity, QString> m_actionLabels;
-    QString m_highlightExplanation;
-
-    QFileInfo *m_dateReference;
-    QStringList m_selectedFiles;
-
-    bool m_gridlyLayout;
-    int m_lastGridlyCount;
-    QList<QWidget *> m_boxes;
-    QWidget *m_boxesParent;
-
-    void layoutBoxesGridly(int count);
-    void layoutBoxesLinearly();
-    void setNoModificationsLabelText();
-    QString labelFor(FileStates::State, bool addHighlightExplanation = false);
-    void setLabelFor(QWidget *w, FileStates::State, bool addHighlightExplanation);
-
-    QStringList getSelectedFilesInState(FileStates::State s) const;
-    QStringList getSelectedFilesSupportingActivity(FileStates::Activity) const;
-};
-
-#endif
--- a/grapher.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,603 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "grapher.h"
-#include "connectionitem.h"
-#include "dateitem.h"
-#include "debug.h"
-#include "changesetscene.h"
-
-#include <QSettings>
-
-#include <iostream>
-
-Grapher::Grapher(ChangesetScene *scene) :
-    m_scene(scene)
-{
-    QSettings settings;
-    settings.beginGroup("Presentation");
-    m_showDates = (settings.value("dateformat", 0) == 1);
-}
-
-int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
-{
-    int col = parent;
-    if (preferParentCol) {
-        if (isAvailable(row, col)) return col;
-    }
-    while (col > 0) {
-        if (isAvailable(row, --col)) return col;
-    }
-    while (col < 0) {
-        if (isAvailable(row, ++col)) return col;
-    }
-    col = parent;
-    int sign = (col < 0 ? -1 : 1);
-    while (1) {
-        col += sign;
-        if (isAvailable(row, col)) return col;
-    }
-}
-
-bool Grapher::isAvailable(int row, int col)
-{
-    if (m_alloc.contains(row) && m_alloc[row].contains(col)) return false;
-    if (!m_haveAllocatedUncommittedColumn) return true;
-    if (!m_uncommitted) return true;
-    return !(row <= m_uncommittedParentRow && col == m_uncommitted->column());
-}
-
-void Grapher::layoutRow(QString id)
-{
-    if (m_handled.contains(id)) {
-        return;
-    }
-    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_changesets[id];
-    ChangesetItem *item = m_items[id];
-    DEBUG << "layoutRow: Looking at " << id.toStdString() << endl;
-
-    int row = 0;
-    int nparents = cs->parents().size();
-
-    if (nparents > 0) {
-        bool haveRow = false;
-        foreach (QString parentId, cs->parents()) {
-
-            if (!m_changesets.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;
-            }
-        }
-        row = row - 1;
-    }
-
-    // row is now an upper bound on our eventual row (because we want
-    // to be above all parents).  But we also want to ensure we are
-    // above all nodes that have earlier dates (to the nearest day).
-    // m_rowDates maps each row to a date: use that.
-
-    QString date;
-    if (m_showDates) {
-        date = cs->date();
-    } else {
-        date = cs->age();
-    }
-    while (m_rowDates.contains(row) && m_rowDates[row] != date) {
-        --row;
-    }
-
-    // We have already laid out all nodes that have earlier timestamps
-    // than this one, so we know (among other things) that we can
-    // safely fill in this row has having this date, if it isn't in
-    // the map yet (it cannot have an earlier date)
-
-    if (!m_rowDates.contains(row)) {
-        m_rowDates[row] = date;
-    }
-
-    // If we're the parent of the uncommitted item, make a note of our
-    // row (we need it later, to avoid overwriting the connecting line)
-    if (!m_uncommittedParents.empty() && m_uncommittedParents[0] == id) {
-        m_uncommittedParentRow = row;
-    }
-
-    DEBUG << "putting " << cs->id().toStdString() << " at row " << row 
-          << endl;
-
-    item->setRow(row);
-    m_handled.insert(id);
-}
-
-void Grapher::layoutCol(QString id)
-{
-    if (m_handled.contains(id)) {
-        DEBUG << "Already looked at " << id.toStdString() << endl;
-        return;
-    }
-    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_changesets[id];
-    DEBUG << "layoutCol: Looking at " << id.toStdString() << endl;
-
-    ChangesetItem *item = m_items[id];
-
-    int col = 0;
-    int row = item->row();
-    QString branch = cs->branch();
-
-    int nparents = cs->parents().size();
-    QString parentId;
-    int parentsOnSameBranch = 0;
-
-    switch (nparents) {
-
-    case 0:
-        col = m_branchHomes[cs->branch()];
-        col = findAvailableColumn(row, col, true);
-        break;
-
-    case 1:
-        parentId = cs->parents()[0];
-
-        if (!m_changesets.contains(parentId) ||
-            !m_changesets[parentId]->isOnBranch(branch)) {
-            // new branch
-            col = m_branchHomes[branch];
-        } else {
-            col = m_items[parentId]->column();
-        }
-
-        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]->isOnBranch(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;
-    }
-
-    DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl;
-
-    m_alloc[row].insert(col);
-    item->setColumn(col);
-    m_handled.insert(id);
-
-    // If we're the first parent of the uncommitted item, it should be
-    // given the same column as us (we already noted that its
-    // connecting line would end at our row)
-
-    if (m_uncommittedParents.contains(id)) {
-        if (m_uncommittedParents[0] == id) {
-            int ucol = findAvailableColumn(row-1, col, true);
-            m_uncommitted->setColumn(ucol);
-            m_haveAllocatedUncommittedColumn = true;
-        }
-        // also, if the uncommitted item has a different branch from
-        // any of its parents, tell it to show the branch
-        if (!cs->isOnBranch(m_uncommitted->branch())) {
-            DEBUG << "Uncommitted branch " << m_uncommitted->branch()
-                  << " differs from my branch " << cs->branch()
-                  << ", asking it to show branch" << endl;
-            m_uncommitted->setShowBranch(true);
-        }
-    }
-
-
-    // Normally the children will lay out themselves, but we can do
-    // a better job in some special cases:
-
-    int nchildren = cs->children().size();
-
-    // look for merging children and children distant from us but in a
-    // straight line, and make sure nobody is going to overwrite their
-    // connection lines
-
-    foreach (QString childId, cs->children()) {
-        DEBUG << "reserving connection line space" << endl;
-        if (!m_changesets.contains(childId)) continue;
-        Changeset *child = m_changesets[childId];
-        int childRow = m_items[childId]->row();
-        if (child->parents().size() > 1 ||
-            child->isOnBranch(cs->branch())) {
-            for (int r = row-1; r > childRow; --r) {
-                m_alloc[r].insert(col);
-            }
-        }
-    }
-
-    // look for the case where exactly two children have the same
-    // branch as us: split them to a little either side of our position
-
-    if (nchildren > 1) {
-        QList<QString> special;
-        foreach (QString childId, cs->children()) {
-            if (!m_changesets.contains(childId)) continue;
-            Changeset *child = m_changesets[childId];
-            if (child->isOnBranch(branch) &&
-                child->parents().size() == 1) {
-                special.push_back(childId);
-            }
-        }
-        if (special.size() == 2) {
-            DEBUG << "handling split-in-two for children " << special[0] << " and " << special[1] << endl;
-            for (int i = 0; i < 2; ++i) {
-                int off = i * 2 - 1; // 0 -> -1, 1 -> 1
-                ChangesetItem *it = m_items[special[i]];
-                it->setColumn(findAvailableColumn(it->row(), col + off, true));
-                for (int r = row-1; r >= it->row(); --r) {
-                    m_alloc[r].insert(it->column());
-                }
-                m_handled.insert(special[i]);
-            }
-        }
-    }
-}
-
-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;
-    m_branchHomes["default"] = 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) {
-                if (home % 2 == 1) {
-                    home = -home;
-                } else {
-                    home = home + 1;
-                }
-            } else {
-                if ((-home) % 2 == 1) {
-                    home = home + 1;
-                } else {
-                    home = -(home-2);
-                }
-            }
-        }
-        m_branchHomes[branch] = home;
-    }
-
-    foreach (QString branch, m_branchRanges.keys()) {
-        DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl;
-    }
-}
-
-static bool
-compareChangesetsByDate(Changeset *const &a, Changeset *const &b)
-{
-    return a->timestamp() < b->timestamp();
-}
-
-ChangesetItem *
-Grapher::getItemFor(Changeset *cs)
-{
-    if (!cs || !m_items.contains(cs->id())) return 0;
-    return m_items[cs->id()];
-}
-
-void Grapher::layout(Changesets csets,
-                     QStringList uncommittedParents,
-                     QString uncommittedBranch)
-{
-    m_changesets.clear();
-    m_items.clear();
-    m_alloc.clear();
-    m_branchHomes.clear();
-
-    m_uncommittedParents = uncommittedParents;
-    m_haveAllocatedUncommittedColumn = false;
-    m_uncommittedParentRow = 0;
-    m_uncommitted = 0;
-
-    DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl;
-
-    if (csets.empty()) return;
-
-    // Create (but don't yet position) the changeset items
-
-    foreach (Changeset *cs, csets) {
-
-        QString id = cs->id();
-
-        if (id == "") {
-            throw LayoutException("Changeset has no ID");
-        }
-        if (m_changesets.contains(id)) {
-            DEBUG << "Duplicate changeset ID " << id
-                  << " in Grapher::layout()" << endl;
-            throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
-        }
-
-        m_changesets[id] = cs;
-
-        ChangesetItem *item = new ChangesetItem(cs);
-        item->setX(0);
-        item->setY(0);
-        item->setZValue(0);
-        m_items[id] = item;
-        m_scene->addChangesetItem(item);
-    }
-
-    // Add the connecting lines
-
-    foreach (Changeset *cs, csets) {
-        QString id = cs->id();
-        ChangesetItem *item = m_items[id];
-        bool merge = (cs->parents().size() > 1);
-        foreach (QString parentId, cs->parents()) {
-            if (!m_changesets.contains(parentId)) continue;
-            Changeset *parent = m_changesets[parentId];
-            parent->addChild(id);
-            ConnectionItem *conn = new ConnectionItem();
-            if (merge) conn->setConnectionType(ConnectionItem::Merge);
-            conn->setChild(item);
-            conn->setParent(m_items[parentId]);
-            conn->setZValue(-1);
-            m_scene->addItem(conn);
-        }
-    }
-    
-    // Add uncommitted item and connecting line as necessary
-
-    if (!m_uncommittedParents.empty()) {
-
-        m_uncommitted = new UncommittedItem();
-        m_uncommitted->setBranch(uncommittedBranch);
-        m_uncommitted->setZValue(10);
-        m_scene->addUncommittedItem(m_uncommitted);
-
-        bool haveParentOnBranch = false;
-        foreach (QString p, m_uncommittedParents) {
-            ConnectionItem *conn = new ConnectionItem();
-            conn->setConnectionType(ConnectionItem::Merge);
-            ChangesetItem *pitem = m_items[p];
-            conn->setParent(pitem);
-            conn->setChild(m_uncommitted);
-            conn->setZValue(0);
-            m_scene->addItem(conn);
-            if (pitem) {
-                if (pitem->getChangeset()->branch() == uncommittedBranch) {
-                    haveParentOnBranch = true;
-                }
-            }
-        }
-
-        // If the uncommitted item has no parents on the same branch,
-        // tell it it has a new branch (the "show branch" flag is set
-        // elsewhere for this item)
-        m_uncommitted->setIsNewBranch(!haveParentOnBranch);
-    }
-
-    // Add the branch labels
-
-    foreach (Changeset *cs, csets) {
-        QString id = cs->id();
-        ChangesetItem *item = m_items[id];
-        bool haveChildOnSameBranch = false;
-        foreach (QString childId, cs->children()) {
-            Changeset *child = m_changesets[childId];
-            if (child->branch() == cs->branch()) {
-                haveChildOnSameBranch = true;
-                break;
-            }
-        }
-        item->setShowBranch(!haveChildOnSameBranch);
-    }
-
-    // We need to lay out the changesets in forward chronological
-    // order.  We have no guarantees about the order in which
-    // changesets appear in the list -- in a simple repository they
-    // will generally be reverse chronological, but that's far from
-    // guaranteed.  So, sort explicitly using the date comparator
-    // above
-
-    qStableSort(csets.begin(), csets.end(), compareChangesetsByDate);
-
-    foreach (Changeset *cs, csets) {
-        DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp()
-              << ", date " << cs->datetime().toStdString() << endl;
-    }
-
-    m_handled.clear();
-    foreach (Changeset *cs, csets) {
-        layoutRow(cs->id());
-    }
-
-    allocateBranchHomes(csets);
-
-    m_handled.clear();
-    foreach (Changeset *cs, csets) {
-        foreach (QString parentId, cs->parents()) {
-            if (!m_handled.contains(parentId) &&
-                m_changesets.contains(parentId)) {
-                layoutCol(parentId);
-            }
-        }
-        layoutCol(cs->id());
-    }
-
-    // Find row and column extents.  We know that 0 is an upper bound
-    // on row, and that mincol must be <= 0 and maxcol >= 0, so these
-    // initial values are good
-
-    int minrow = 0, maxrow = 0;
-    int mincol = 0, maxcol = 0;
-
-    foreach (int r, m_alloc.keys()) {
-        if (r < minrow) minrow = r;
-        if (r > maxrow) maxrow = r;
-        ColumnSet &c = m_alloc[r];
-        foreach (int i, c) {
-            if (i < mincol) mincol = i;
-            if (i > maxcol) maxcol = i;
-        }
-    }
-
-    int datemincol = mincol, datemaxcol = maxcol;
-
-    if (mincol == maxcol) {
-        --datemincol;
-        ++datemaxcol;
-    } else if (m_alloc[minrow].contains(mincol)) {
-        --datemincol;
-    }
-
-    // We've given the uncommitted item a column, but not a row yet --
-    // it always goes at the top
-
-    if (m_uncommitted) {
-        --minrow;
-        DEBUG << "putting uncommitted item at row " << minrow << endl;
-        m_uncommitted->setRow(minrow);
-    }
-
-    // Changeset items that have nothing to either side of them can be
-    // made double-width
-
-    foreach (Changeset *cs, csets) {
-        ChangesetItem *item = m_items[cs->id()];
-        if (isAvailable(item->row(), item->column()-1) &&
-            isAvailable(item->row(), item->column()+1)) {
-            item->setWide(true);
-        }
-    }
-
-    if (m_uncommitted) {
-        if (isAvailable(m_uncommitted->row(), m_uncommitted->column()-1) &&
-            isAvailable(m_uncommitted->row(), m_uncommitted->column()+1)) {
-            m_uncommitted->setWide(true);
-        }
-    }
-
-    QString prevDate;
-    int changeRow = 0;
-
-    bool even = false;
-    int n = 0;
-
-    for (int row = minrow; row <= maxrow; ++row) {
-
-        QString date = m_rowDates[row];
-        n++;
-
-        if (date != prevDate) {
-            if (prevDate != "") {
-                DateItem *item = new DateItem();
-                item->setDateString(prevDate);
-                item->setCols(datemincol, datemaxcol - datemincol + 1);
-                item->setRows(changeRow, n);
-                item->setEven(even);
-                item->setZValue(-2);
-                m_scene->addDateItem(item);
-                even = !even;
-            }
-            prevDate = date;
-            changeRow = row;
-            n = 0;
-        }
-    }
-    
-    if (n > 0) {
-        DateItem *item = new DateItem();
-        item->setDateString(prevDate);
-        item->setCols(datemincol, datemaxcol - datemincol + 1);
-        item->setRows(changeRow, n+1);
-        item->setEven(even);
-        item->setZValue(-2);
-        m_scene->addDateItem(item);
-        even = !even;
-    }
-}
-
--- a/grapher.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 GRAPHER_H
-#define GRAPHER_H
-
-#include "changeset.h"
-#include "changesetitem.h"
-#include "uncommitteditem.h"
-#include "changesetscene.h"
-
-#include <QSet>
-#include <QMap>
-#include <QPair>
-
-#include <exception>
-
-class Grapher
-{
-public:
-    Grapher(ChangesetScene *scene);
-
-    void layout(Changesets csets,
-                QStringList uncommittedParents,
-                QString uncommittedBranch);
-
-    ChangesetItem *getItemFor(Changeset *cs);
-
-    UncommittedItem *getUncommittedItem() { return m_uncommitted; }
-
-    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:
-    ChangesetScene *m_scene;
-
-    typedef QMap<QString, Changeset *> IdChangesetMap;
-    IdChangesetMap m_changesets;
-
-    typedef QMap<QString, ChangesetItem *> IdItemMap;
-    IdItemMap m_items;
-
-    typedef QSet<int> ColumnSet;
-    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;
-
-    typedef QMap<int, QString> RowDateMap;
-    RowDateMap m_rowDates;
-
-    bool m_showDates;
-
-    QStringList m_uncommittedParents;
-    int m_uncommittedParentRow;
-    UncommittedItem *m_uncommitted;
-    bool m_haveAllocatedUncommittedColumn;
-
-    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);
-    bool isAvailable(int row, int col);
-};
-
-#endif 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-04.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,38 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>What is a repository?</h2>
+
+<p>When you use a version control system to keep track of your changes to
+a set of files, there are two different concepts you're dealing with:
+a <i>working copy</i> and a <i>repository</i>.</p>
+
+<p>A <i>working copy</i> is just a folder with your project's files in it.  It
+contains the versions of the files that you are working with now.
+<ul><li>EasyMercurial's &ldquo;My Work&rdquo; tab shows you which files you have been working on in your current working copy.</li></ul></p>
+
+<p>A <i>repository</i> is a record of the entire history of your project. When
+change something in the working copy, you can then commit it to the
+repository and your change gets added to the history.
+<ul><li>EasyMercurial's &ldquo;History&rdquo; tab shows you the changes that have been committed to your project's history in its repository.</li></ul></p>
+
+<p>You can also go back and grab an older version from the repository if
+you find you need it.  (If you do this, then the working copy will be
+updated so as to contain that older version rather than the most
+recent one.)</p>
+
+<p>Older centralised version control systems use a separate database for
+the repository.  But with a distributed version control system such as
+Mercurial, the repository &ndash; the entire history of your project files
+&ndash; is stowed into a special folder inside the working copy on your
+hard drive.  Every change you commit gets added to the history in that
+hidden folder. (The history is compressed, so it doesn't take as much
+space as you might imagine.)</p>
+
+<p>The term <i>remote repository</i> simply refers to a repository related to
+your local one, but stored on another computer somewhere else.  Often
+this may be a &ldquo;master copy&rdquo; of your project stored on a server
+elsewhere, which you and your collaborators can use to keep up with
+each other's work, or which you can use to make your work public, or
+simply use as a private backup.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-10.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,37 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>Someone gave me a repository URL and asked me to clone it</h2>
+
+<p>A Mercurial repository location is usually described by a URL, like
+that of a website.</p>
+
+<p>For example, the URL for the repository containing the source code for
+EasyMercurial itself is <code>https://bitbucket.org/cannam/easyhg</code>.</p>
+
+<p>To get a copy of the files in a repository, you need to <i>clone</i> the
+repository from the remote URL into a folder on your own computer.  To
+do this,</p>
+
+<p><b>1. Click the Open toolbar button or use File -> Open</b></p>
+
+<p><center><img src="images/openremote50.png"></center></p>
+
+<p><b>2. Select &ldquo;Remote repository&rdquo; as the thing you want to open</b></p>
+
+<p><b>3. Enter the repository URL into the URL field</b></p>
+
+<p><b>4. Give the name of a folder on your local computer to clone into</b> &ndash;
+ this folder will be created for you, so it shouldn't be one that already
+ exists</p>
+
+<p><b>5. Click OK</b></p>
+
+<p>If the remote repository has restricted access, you may be asked to
+provide a username and password to log in to the server it is hosted
+on.  If the repository is large, you may have to wait a while for all the
+data to be transferred.</p>
+
+<p>Provided the clone has been successful, you should now have a local
+repository to start working in.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg clone</b></li></ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-11.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,27 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I have a folder of source code or documents on my computer and I want to use version control to manage them</h2>
+
+<p>To start using version control for a project folder, you need to
+initialise a repository there.  EasyMercurial does this for you when
+you open the folder.</p>
+
+<p><b>1. Click the Open toolbar button or use File -> Open</b></p>
+
+<p><center><img src="images/openfolder50.png"></center></p>
+
+<p><b>2. Select &ldquo;File folder&rdquo; as the thing you want to open</b></p>
+
+<p><b>3. Browse to your folder</b></p>
+
+<p><b>4. Click OK</b></p>
+
+<p>A new repository will be created, stowed into the working folder you
+selected.  At first, it will have an empty history.  You can then
+start to add and commit changes to your files.</p>
+
+<p>(You will need to &ldquo;add&rdquo; files before you can start to track changes to
+them.  The default is for all files in the folder to be treated as
+&ldquo;untracked&rdquo;, i.e. not included in the history.)
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg init</b></li></ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-12.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,22 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I want to start a new project using version control</h2>
+
+<p>You can initialise a repository in an empty folder, in order to start
+using version control for a new project:</p>
+
+<p><b>1. Click the Open toolbar button or use File -> Open</b></p>
+
+<p><center><img src="images/openfolder50.png"></center></p>
+
+<p><b>2. Select &ldquo;File folder&rdquo; as the thing you want to open</b></p>
+
+<p><b>3. Make a new folder in the file dialog and browse to it</b></p>
+
+<p><b>4. Click OK</b></p>
+
+<p>A new repository will be created, stowed into the empty working folder
+you selected.  At first, it will have an empty history.  You can then
+start to add files and commit changes to your files.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg init</b></li></ul>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-13.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,17 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I have a Mercurial repository on my local computer already and I want to use it with EasyMercurial</h2>
+
+<p>Just open it:</p>
+
+<p><b>1. Click the Open toolbar button or use File -> Open</b></p>
+
+<p><center><img src="images/openlocal50.png"></center></p>
+
+<p><b>2. Select &ldquo;Local repository&rdquo; as the thing you want to open</b></p>
+
+<p><b>3. Browse to the working folder for your local repository</b></p>
+
+<p><b>4. Click OK</b></p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-20.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,30 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I've added a new file: what do I do with it?</h2>
+
+<p>When you add a new file in the working folder, you normally want to
+ensure that Mercurial keeps track of changes to that file &ndash; and that
+the file is included in all copies of the repository.  To do this, you
+need to tell Mercurial to <i>track</i> the file by adding it to version
+control.</p>
+
+<p>EasyMercurial shows files that have been created but not added in the
+<b>&ldquo;Untracked&rdquo;</b> file list under &ldquo;My work&rdquo;.  (If your file is not listed
+there, try clicking the Refresh button.)</p>
+
+<p><b>1. Find the file you want to add in the Untracked list and select it</b></p>
+
+<p><b>2. Click Add in the toolbar on the left of the window</b></p>
+
+<p>The file will be moved to the <b>&ldquo;Added&rdquo;</b> list.  This tells Mercurial to
+track the file.  The next time you commit, the contents of your new
+file will be recorded as part of that change set.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg add</b></li></ul></p>
+
+<p>Of course, you don't always want to track every file in your working
+copy.  Object files generated by a compiler, output files from tests,
+etc should often not be included in version control.  You can ensure
+that such files don't show up in the Untracked list by right-clicking
+on them and choosing <b>&ldquo;Ignore..."</b>.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-21.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,22 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I have changed some files and I want to record the changes</h2>
+
+<p>Click the Commit button in the toolbar on the left to commit all of
+the changes you have made to tracked files in your working folder.
+That is, all files listed as <b>&ldquo;Modified&rdquo;</b>, <b>&ldquo;Removed&rdquo;</b>, or <b>&ldquo;Added&rdquo;</b>
+under &ldquo;My Work&rdquo;.</p>
+
+<p>(If the files you have changed are still listed as <b>&ldquo;Untracked&rdquo;</b>, then
+you must add them before you can commit.  See <a href="a-20.html">I've added a new file...</a>.</p>
+
+<p>When you commit your changes, you will be asked for a commit message
+which will accompany that change set in the history.  Enter something
+that will help you remember &ndash; and other readers understand &ndash; what
+you have changed and why.</p>
+
+<p>If you want to commit only some files, right-click on them in the list
+and choose Commit from the context menu.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg commit</b></li></ul></p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-22.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,33 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I want to remove, rename, or copy a file</h2>
+
+<p>To remove or rename a file, you first need to find it in the list of
+files under &ldquo;My work&rdquo;.  This area normally shows only those files that
+you have changed since your last commit: if it isn't one of those,
+toggle the &ldquo;Show all files&rdquo; option at the bottom of the window.</p>
+
+<p>To <b>remove</b> a file from version control so that changes to it are no longer tracked:</p>
+
+<p><b>1. Select the file you want to remove in the list of files under &ldquo;My work&rdquo;.</b></p>
+
+<p><b>2. Click Remove in the toolbar on the left of the window</b></p>
+
+<p>The file will be moved to the <b>&ldquo;Removed&rdquo;</b> list.  This tells Mercurial
+to stop tracking the file the next time you commit.  The file itself
+is not removed from the disc: you will need to do that using your
+system file manager afterwards.
+<ul><li>Note: the equivalent Mercurial command for this is <b>hg remove -Af</b></li></ul></p>
+
+<p>To <b>rename</b> or <b>copy</b> a file:</p>
+
+<p><b>1. Select the file you want to remove in the list of files under &ldquo;My work&rdquo;.</b></p>
+
+<p><b>2. Right-click and select &ldquo;Rename..." or &ldquo;Copy..." on the context menu</b></p>
+
+<p><b>3. Enter a new name for the file.</b>
+<ul><li>Note: the equivalent Mercurial commands are <b>hg rename</b> and <b>hg copy</b></li></ul></p>
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-23.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,19 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I just deleted a file using the system file manager &ndash; then remembered I hadn't told the version control tool about it &ndash; what now?</h2>
+
+<p>Any files that the version control system thinks should be there, but
+that can't be found in your working copy are shown as <b>&ldquo;Missing&rdquo;</b>
+under &ldquo;My work&rdquo;.  All you need to do is:</p>
+
+<p><b>1. Find your file in the &ldquo;Missing&rdquo; list and select it</b></p>
+
+<p><b>2. Click Remove in the toolbar on the left of the window</b></p>
+
+<p>This tells Mercurial that you haven't merely lost the file, but that
+you intended to remove it.  The next time you commit, it will be
+removed from tracking in version control.</p>
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-30.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,11 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I have committed some changes &ndash; how do I share them with my colleagues?</h2>
+
+<p>There are two common general approaches:</p>
+
+<p><a href="a-31.html">Permit your colleagues to &ldquo;pull&rdquo; your changes</a> directly from the local repository in your working folder, <i>or</i></p>
+
+<p><a href="a-32.html">&ldquo;Push&rdquo; your changes to a shared remote repository</a> which you can all use as a master copy.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-31.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,21 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I want to let my colleagues pull changes directly from my local repository</h2>
+
+<p>You can do this on a temporary basis using EasyMercurial, for
+occasional ad-hoc transfers:</p>
+
+<p><b>1. In EasyMercurial on your computer, go to File -> Share Repository and make a note of the URL shown in the window that is opened</b></p>
+
+<p><b>2. In EasyMercurial on your colleague's computer, either <a href="a-12.html">open a new empty folder</a>, or <a href="a-13.html">reopen a repository</a> that has been pulled from your repository in the past</b></p>
+
+<p><b>3. Go to Remote -> Set Remote Location on your colleague's computer and enter the URL you made a note of in the first step</b></p>
+
+<p><b>4. Click Pull in the main toolbar on your colleague's EasyMercurial program.</b></p>
+
+<p>There are various ways to set this relationship on a less temporary
+footing if you have shared access to your local folder, through
+network filesystems or via remote login to the computer you are using.
+Configuring this is outside the scope of this help document.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-32.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,37 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I want to put my changes into a master repository shared with my colleagues</h2>
+
+<p>Setting up such a repository with a properly configured remote server
+is out of the scope of this Help, but you generally want one of the
+following:</p>
+
+<p><b>A server that everyone on your team has secure ssh access to</b>, <i>or</i></p>
+
+<p><b>An account with a managed online Mercurial hosting service</b></p>
+
+<p>With either of the above, you should be able to create a new
+repository on the server and obtain a Mercurial URL for it.  That may
+be a <i>ssh://host/path</i> URL in the former case, or the URL (often an
+<i>https</i> one) provided by the service in the latter case.</p>
+
+<p>In EasyMercurial, you then:</p>
+
+<p><b>1. Go to Remote -> Set Remote Location.., enter the URL of the remote repository and click OK.</b>
+<ul><li>This tells EasyMercurial to use that URL as the default location for subsequent push and pull operations.</li></ul></p>
+
+<p><b>2. Click Push on the main toolbar at the top of the EasyMercurial window.</b></p>
+
+<p>This will push all of the changes that you have made in your local
+repository (since you pushed to the same target, if you ever have).
+You should do this regularly whenever you have a coherent set of
+changes for others to use or test.  Your colleagues can then pull from
+the same remote repository URL to obtain your changes.</p>
+
+<p>For this to work, the target repository must be <i>related</i> to the local
+one.  That means either a repository that has been pulled to, or
+pushed to from, the local repository before; or the repository that
+was initially used to clone the local one from; or else an empty
+repository.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-33.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,40 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>I tried to push my changes, but it told me &ldquo;the remote repository may have been changed by someone else&rdquo; and refused</h2>
+
+<p>This indicates that the remote repository has some changes in it that
+you do not have in your local repository (and that are in branches
+that you have also changed).</p>
+
+<p>Perhaps someone else made these changes and pushed them, or they may
+have been pushed by you from a different computer.</p>
+
+<p><b>Why should that prevent me from pushing my changes?</b></p>
+
+<p>A good principle is that you should review and test your changes
+before you push them to another repository.  It may be OK to commit
+changes locally that don't really work or that aren't complete enough
+to test, but it's a bad idea to push anything that would cause the
+remote repository to have an untested set of changes in it.</p>
+
+<p>For this reason, if you change some files, someone else changes some
+others, and you both try to push them without knowing about the other
+one, Mercurial must refuse the second push &ndash; it can't simply merge
+the changes because the result might not make any sense.</p>
+
+<p>Instead you must pull the other person's changes and merge them
+locally before you push.  Fortunately, this is easy to do:</p>
+
+<p><b>1. Click Pull on the main toolbar at the top of the EasyMercurial window.</b>
+<ul><li>You should see that some changes are pulled and added to your local repository.  This will usually lead to a forked graph in the History pane, as your changes and the other user's were both started from the same parent at the same time.</li></ul></p>
+
+<p><b>2. Click Merge in the toolbar on the left.</b>
+<ul><li>Any changes that affect different files, or that affect different parts of the same file, will be merged automatically.  For changes that affect the same parts of the same file, you will be asked to choose which change to include in the merged copy.  See <a href="a-34.html">How do I merge my changes...</a> for more about this.</li></ul></p>
+
+<p><b>3. Review or test the resulting merged version in your local working folder.</b></p>
+
+<p><b>4. Commit the merged version.</b></p>
+
+<p><b>5. Push again to the remote repository.</b></p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-34.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,30 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>Every time I push some changes, I have to type my password again</h2>
+
+<p>When you push to a remote repository with an &ldquo;https:" prefix on its
+URL, EasyMercurial's password dialog should ask whether you want to
+remember your login details.</p>
+
+<p>If you say yes, the details will be stored until EasyMercurial
+exits&mdash;so you won't have to enter them if you push or pull again
+while the present EasyMercurial session is running. Your password will
+be stored, encrypted with a session key, in a file which is deleted
+when EasyMercurial exits.</p>
+
+<p><b>If this option does not appear</b></p>
+
+<p>... and if you're using OS/X, then you need to install the PyCrypto
+library before EasyMercurial will be able to store encrypted passwords
+for you.</p>
+
+<p>Try running <code>sudo easy_install pycrypto</code> in a terminal window.</p>
+
+<p><b>Not using an https repository?</b></p>
+
+<p>If your remote repository uses ssh or some other protocol, then
+EasyMercurial won't be able to help. You might consider using ssh
+public key authentication and an ssh agent&mdash;talk to the admin of your
+remote repository for more details.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-35.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,57 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>How do I use the Merge window?</h2>
+
+<p>If you are working with other people on a project, it is likely that
+at some point you'll find that more than one of you have edited the
+same lines in the same file, in different ways.</p>
+
+<p>When that happens and you try to merge the two versions, EasyMercurial
+will pop up a merge window that looks a bit like this.</p>
+
+<p><center><img src="images/merge50.png"></center></p>
+
+<p>(This is actually a separate program, not part of EasyMercurial: it's
+a merge tool called kdiff3.)</p>
+
+<p>This looks a bit complicated, but it is there to help you pick which
+of the changes from each of the two &ldquo;rival&rdquo; versions of the file you
+want to use in your merged version.</p>
+
+<p> <b>The top middle one is <i>your current version</i>.</b> That's
+  the one you had before you decided to merge the other version into
+  it.</p>
+
+<p> <b>At top right is <i>the version you're merging</i>.</b> That's the other
+  rival version &ndash; the one that the other person can see in their copy
+  of the repository.</p>
+
+<p> <b>At top left is <i>the common ancestor</i>.</b> That's the version that both
+  of you had, before you started editing it in different ways.</p>
+
+<p> <b>At the bottom is the output.</b></p>
+
+<p>Each <i>conflict</i> (a line which you have both changed in different ways)
+is shown with <b>Merge Conflict</b> in the output at the bottom.  To sort
+out the conflicts and get the right output, you need to go through
+them one by one, for each one pressing the A, B, or C button to say
+whether for this line you want the top-left, top-middle, or top-right
+version to &ldquo;win&rdquo;.  (You can choose more than one, if you want both
+versions of a line to appear.)</p>
+
+<p>Then having resolved a conflict, press the three-arrows-down toolbar
+button to go to the next one &ndash; and when you've done them all, save
+and exit.</p>
+
+<p>The most important thing to remember is that <i>whatever appears in the
+bottom pane is what you'll get as a result</i>.</p>
+
+<p>No matter how confusing the process, just remember that if it looks
+correct in the bottom pane, it will be correct when you save and
+exit. You can even edit the file directly in the bottom pane if you
+aren't happy with the way the merge is going.</p>
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/a-40.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,9 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+
+<h2>Video tutorials</h2>
+
+<p>There are some very simple video tutorials available on the EasyMercurial website.</p>
+
+<p>Please see <a href="http://easyhg.org/videos.html">http://easyhg.org/videos.html</a>.</p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/generate.sh	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+css='<link rel="stylesheet" type="text/css" href="help.css"/>'
+
+echo "$css" > topics.html
+cat intro.html >> topics.html
+
+pcat=""
+
+for x in topics/*.txt ; do
+
+    b=`basename "$x" .txt`
+    out="a-$b.html"
+
+    echo "$css" > "$out"
+
+    cat "$x" | perl -e '
+$_ = join "", <>;
+s/^{[\w\s]+}//s;
+s/^(\s*)([A-Za-z][^\n]*)/$1<h2>$2<\/h2>/s;
+s/^\s+\*\s+(.*)$/<ul><li>$1<\/li><\/ul>/gm;
+s/\*([\w"][^\*]+)\*/<b>$1<\/b>/gs;
+s/"([\w])/&ldquo;$1/gs;
+s/([\w])"/$1&rdquo;/gs;
+s/^\#([^\s]+)$/<center><img src="images\/$1.png"><\/center>/gm;
+s/\n-+\n/\n/gs;
+s/\n\n([^\n])/\n\n<p>$1/gs;
+s/^\n*([^<\n])/\n<p>$1/gs;
+s/^\n*(<[^p])/\n<p>$1/gs;
+s/([^\n])\n\n/$1<\/p>\n\n/gs;
+s/([^>\n])\n*$/$1<\/p>\n\n/gs;
+s/\[\[([^\|]*)\|([^\]]*)\]\]/<a href="a-$1.html">$2<\/a>/gs;
+s/\[\[([^\|\]]*)\]\]/<a href="$1">$1<\/a>/gs;
+s/\b_([^_]+)_\b/<i>$1<\/i>/gs;
+s/@(\w[^@]+)@/<code>$1<\/code>/gs;
+s/---/&mdash;/gs;
+s/--/&ndash;/gs;
+s/<p><h2>/<h2>/gs;
+s/<\/h2><\/p>/<\/h2>/gs;
+print;
+' >> "$out"
+
+    category=`grep '^{.*}$' "$x" | sed 's/[{}]//g'`
+
+    if [ "$category" != "$pcat" ]; then
+	echo "<h3>$category</h3>" >> topics.html
+	pcat="$category"
+    fi
+
+    grep '<h2>' "$out" | sed "s|<h2>|<p><a href=\"$out\">|" | sed 's/<\/h2>/<\/a><\/p>/' >> topics.html
+
+done
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/help.css	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,59 @@
+
+body {
+  background: #fdfaf0;
+  color: #3e442c;
+  margin: 0;
+  padding: 0;
+  margin-bottom: 40px;
+  font-family: sans-serif;
+}
+
+h1, h2, h3, h4 {
+  margin-left: 10px;
+  margin-bottom: 0.4em;
+}
+
+h2 {
+  font-size: 1.3em;
+  margin-top: 0;
+}
+
+h3 {
+  font-size: 1.2em;
+}
+
+h4 { 
+  font-size: 1.1em;
+}
+
+ol, ul, ol li, ul li {
+  margin-left: 0;
+  color: #808080;
+  font-style: italic;
+}
+
+p, pre { 
+  margin-left: 20px;
+  margin-bottom: 0.5em;
+  margin-right: 10px;
+  margin-top: 0;
+}
+
+blockquote {
+  margin-left: 70px;
+}
+
+table { 
+  padding-top: 0;
+}
+
+a {
+  color: #be5700;
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+
Binary file help/images/merge.png has changed
Binary file help/images/merge50.png has changed
Binary file help/images/openfolder.png has changed
Binary file help/images/openfolder50.png has changed
Binary file help/images/openlocal.png has changed
Binary file help/images/openlocal50.png has changed
Binary file help/images/openremote.png has changed
Binary file help/images/openremote50.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/intro.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,4 @@
+
+<h1>Quick Topics</h1>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics.html	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,26 @@
+<link rel="stylesheet" type="text/css" href="help.css"/>
+
+<h1>Quick Topics</h1>
+
+
+<h3>Terminology</h3>
+<p><a href="a-04.html">What is a repository?</a></p>
+<h3>Opening and initialising things</h3>
+<p><a href="a-10.html">Someone gave me a repository URL and asked me to clone it</a></p>
+<p><a href="a-11.html">I have a folder of source code or documents on my computer and I want to use version control to manage them</a></p>
+<p><a href="a-12.html">I want to start a new project using version control</a></p>
+<p><a href="a-13.html">I have a Mercurial repository on my local computer already and I want to use it with EasyMercurial</a></p>
+<h3>Making changes</h3>
+<p><a href="a-20.html">I've added a new file: what do I do with it?</a></p>
+<p><a href="a-21.html">I have changed some files and I want to record the changes</a></p>
+<p><a href="a-22.html">I want to remove, rename, or copy a file</a></p>
+<p><a href="a-23.html">I just deleted a file using the system file manager &ndash; then remembered I hadn't told the version control tool about it &ndash; what now?</a></p>
+<h3>Sharing changes</h3>
+<p><a href="a-30.html">I have committed some changes &ndash; how do I share them with my colleagues?</a></p>
+<p><a href="a-31.html">I want to let my colleagues pull changes directly from my local repository</a></p>
+<p><a href="a-32.html">I want to put my changes into a master repository shared with my colleagues</a></p>
+<p><a href="a-33.html">I tried to push my changes, but it told me &ldquo;the remote repository may have been changed by someone else&rdquo; and refused</a></p>
+<p><a href="a-34.html">Every time I push some changes, I have to type my password again</a></p>
+<p><a href="a-35.html">How do I use the Merge window?</a></p>
+<h3>See also</h3>
+<p><a href="a-40.html">Video tutorials</a></p>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/04.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,39 @@
+{Terminology}
+
+What is a repository?
+
+When you use a version control system to keep track of your changes to
+a set of files, there are two different concepts you're dealing with:
+a _working copy_ and a _repository_.
+
+A _working copy_ is just a folder with your project's files in it.  It
+contains the versions of the files that you are working with now.
+
+ * EasyMercurial's "My Work" tab shows you which files you have been working on in your current working copy.
+
+A _repository_ is a record of the entire history of your project. When
+change something in the working copy, you can then commit it to the
+repository and your change gets added to the history.
+
+ * EasyMercurial's "History" tab shows you the changes that have been committed to your project's history in its repository.
+
+You can also go back and grab an older version from the repository if
+you find you need it.  (If you do this, then the working copy will be
+updated so as to contain that older version rather than the most
+recent one.)
+
+Older centralised version control systems use a separate database for
+the repository.  But with a distributed version control system such as
+Mercurial, the repository -- the entire history of your project files
+-- is stowed into a special folder inside the working copy on your
+hard drive.  Every change you commit gets added to the history in that
+hidden folder. (The history is compressed, so it doesn't take as much
+space as you might imagine.)
+
+The term _remote repository_ simply refers to a repository related to
+your local one, but stored on another computer somewhere else.  Often
+this may be a "master copy" of your project stored on a server
+elsewhere, which you and your collaborators can use to keep up with
+each other's work, or which you can use to make your work public, or
+simply use as a private backup.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/10.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,37 @@
+{Opening and initialising things}
+
+Someone gave me a repository URL and asked me to clone it
+
+A Mercurial repository location is usually described by a URL, like
+that of a website.
+
+For example, the URL for the repository containing the source code for
+EasyMercurial itself is @https://bitbucket.org/cannam/easyhg@.
+
+To get a copy of the files in a repository, you need to _clone_ the
+repository from the remote URL into a folder on your own computer.  To
+do this,
+
+*1. Click the Open toolbar button or use File -> Open*
+
+#openremote50
+
+*2. Select "Remote repository" as the thing you want to open*
+
+*3. Enter the repository URL into the URL field*
+
+*4. Give the name of a folder on your local computer to clone into* --
+ this folder will be created for you, so it shouldn't be one that already
+ exists
+
+*5. Click OK*
+
+If the remote repository has restricted access, you may be asked to
+provide a username and password to log in to the server it is hosted
+on.  If the repository is large, you may have to wait a while for all the
+data to be transferred.
+
+Provided the clone has been successful, you should now have a local
+repository to start working in.
+
+ * Note: the equivalent Mercurial command for this is *hg clone*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/11.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,27 @@
+{Opening and initialising things}
+
+I have a folder of source code or documents on my computer and I want to use version control to manage them
+
+To start using version control for a project folder, you need to
+initialise a repository there.  EasyMercurial does this for you when
+you open the folder.
+
+*1. Click the Open toolbar button or use File -> Open*
+
+#openfolder50
+
+*2. Select "File folder" as the thing you want to open*
+
+*3. Browse to your folder*
+
+*4. Click OK*
+
+A new repository will be created, stowed into the working folder you
+selected.  At first, it will have an empty history.  You can then
+start to add and commit changes to your files.
+
+(You will need to "add" files before you can start to track changes to
+them.  The default is for all files in the folder to be treated as
+"untracked", i.e. not included in the history.)
+
+ * Note: the equivalent Mercurial command for this is *hg init*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/12.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,22 @@
+{Opening and initialising things}
+
+I want to start a new project using version control
+
+You can initialise a repository in an empty folder, in order to start
+using version control for a new project:
+
+*1. Click the Open toolbar button or use File -> Open*
+
+#openfolder50
+
+*2. Select "File folder" as the thing you want to open*
+
+*3. Make a new folder in the file dialog and browse to it*
+
+*4. Click OK*
+
+A new repository will be created, stowed into the empty working folder
+you selected.  At first, it will have an empty history.  You can then
+start to add files and commit changes to your files.
+
+ * Note: the equivalent Mercurial command for this is *hg init*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/13.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,16 @@
+{Opening and initialising things}
+
+I have a Mercurial repository on my local computer already and I want to use it with EasyMercurial
+
+Just open it:
+
+*1. Click the Open toolbar button or use File -> Open*
+
+#openlocal50
+
+*2. Select "Local repository" as the thing you want to open*
+
+*3. Browse to the working folder for your local repository*
+
+*4. Click OK*
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/20.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,30 @@
+{Making changes}
+
+I've added a new file: what do I do with it?
+
+When you add a new file in the working folder, you normally want to
+ensure that Mercurial keeps track of changes to that file -- and that
+the file is included in all copies of the repository.  To do this, you
+need to tell Mercurial to _track_ the file by adding it to version
+control.
+
+EasyMercurial shows files that have been created but not added in the
+*"Untracked"* file list under "My work".  (If your file is not listed
+there, try clicking the Refresh button.)
+
+*1. Find the file you want to add in the Untracked list and select it*
+
+*2. Click Add in the toolbar on the left of the window*
+
+The file will be moved to the *"Added"* list.  This tells Mercurial to
+track the file.  The next time you commit, the contents of your new
+file will be recorded as part of that change set.
+
+ * Note: the equivalent Mercurial command for this is *hg add*
+
+Of course, you don't always want to track every file in your working
+copy.  Object files generated by a compiler, output files from tests,
+etc should often not be included in version control.  You can ensure
+that such files don't show up in the Untracked list by right-clicking
+on them and choosing *"Ignore..."*.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/21.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,22 @@
+{Making changes}
+
+I have changed some files and I want to record the changes
+
+Click the Commit button in the toolbar on the left to commit all of
+the changes you have made to tracked files in your working folder.
+That is, all files listed as *"Modified"*, *"Removed"*, or *"Added"*
+under "My Work".
+
+(If the files you have changed are still listed as *"Untracked"*, then
+you must add them before you can commit.  See [[20|I've added a new file...]].
+
+When you commit your changes, you will be asked for a commit message
+which will accompany that change set in the history.  Enter something
+that will help you remember -- and other readers understand -- what
+you have changed and why.
+
+If you want to commit only some files, right-click on them in the list
+and choose Commit from the context menu.
+
+ * Note: the equivalent Mercurial command for this is *hg commit*
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/22.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,34 @@
+{Making changes}
+
+I want to remove, rename, or copy a file
+
+To remove or rename a file, you first need to find it in the list of
+files under "My work".  This area normally shows only those files that
+you have changed since your last commit: if it isn't one of those,
+toggle the "Show all files" option at the bottom of the window.
+
+To *remove* a file from version control so that changes to it are no longer tracked:
+
+*1. Select the file you want to remove in the list of files under "My work".*
+
+*2. Click Remove in the toolbar on the left of the window*
+
+The file will be moved to the *"Removed"* list.  This tells Mercurial
+to stop tracking the file the next time you commit.  The file itself
+is not removed from the disc: you will need to do that using your
+system file manager afterwards.
+
+ * Note: the equivalent Mercurial command for this is *hg remove -Af*
+
+To *rename* or *copy* a file:
+
+*1. Select the file you want to remove in the list of files under "My work".*
+
+*2. Right-click and select "Rename..." or "Copy..." on the context menu*
+
+*3. Enter a new name for the file.*
+
+ * Note: the equivalent Mercurial commands are *hg rename* and *hg copy*
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/23.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,18 @@
+{Making changes}
+
+I just deleted a file using the system file manager -- then remembered I hadn't told the version control tool about it -- what now?
+
+Any files that the version control system thinks should be there, but
+that can't be found in your working copy are shown as *"Missing"*
+under "My work".  All you need to do is:
+
+*1. Find your file in the "Missing" list and select it*
+
+*2. Click Remove in the toolbar on the left of the window*
+
+This tells Mercurial that you haven't merely lost the file, but that
+you intended to remove it.  The next time you commit, it will be
+removed from tracking in version control.
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/30.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,10 @@
+{Sharing changes}
+
+I have committed some changes -- how do I share them with my colleagues?
+
+There are two common general approaches:
+
+[[31|Permit your colleagues to "pull" your changes]] directly from the local repository in your working folder, _or_
+
+[[32|"Push" your changes to a shared remote repository]] which you can all use as a master copy.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/31.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,20 @@
+{Sharing changes}
+
+I want to let my colleagues pull changes directly from my local repository
+
+You can do this on a temporary basis using EasyMercurial, for
+occasional ad-hoc transfers:
+
+*1. In EasyMercurial on your computer, go to File -> Share Repository and make a note of the URL shown in the window that is opened*
+
+*2. In EasyMercurial on your colleague's computer, either [[12|open a new empty folder]], or [[13|reopen a repository]] that has been pulled from your repository in the past*
+
+*3. Go to Remote -> Set Remote Location on your colleague's computer and enter the URL you made a note of in the first step*
+
+*4. Click Pull in the main toolbar on your colleague's EasyMercurial program.*
+
+There are various ways to set this relationship on a less temporary
+footing if you have shared access to your local folder, through
+network filesystems or via remote login to the computer you are using.
+Configuring this is outside the scope of this help document.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/32.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,37 @@
+{Sharing changes}
+
+I want to put my changes into a master repository shared with my colleagues
+
+Setting up such a repository with a properly configured remote server
+is out of the scope of this Help, but you generally want one of the
+following:
+
+*A server that everyone on your team has secure ssh access to*, _or_
+
+*An account with a managed online Mercurial hosting service*
+
+With either of the above, you should be able to create a new
+repository on the server and obtain a Mercurial URL for it.  That may
+be a _ssh://host/path_ URL in the former case, or the URL (often an
+_https_ one) provided by the service in the latter case.
+
+In EasyMercurial, you then:
+
+*1. Go to Remote -> Set Remote Location.., enter the URL of the remote repository and click OK.*
+
+ * This tells EasyMercurial to use that URL as the default location for subsequent push and pull operations.
+
+*2. Click Push on the main toolbar at the top of the EasyMercurial window.*
+
+This will push all of the changes that you have made in your local
+repository (since you pushed to the same target, if you ever have).
+You should do this regularly whenever you have a coherent set of
+changes for others to use or test.  Your colleagues can then pull from
+the same remote repository URL to obtain your changes.
+
+For this to work, the target repository must be _related_ to the local
+one.  That means either a repository that has been pulled to, or
+pushed to from, the local repository before; or the repository that
+was initially used to clone the local one from; or else an empty
+repository.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/33.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,41 @@
+{Sharing changes}
+
+I tried to push my changes, but it told me "the remote repository may have been changed by someone else" and refused
+
+This indicates that the remote repository has some changes in it that
+you do not have in your local repository (and that are in branches
+that you have also changed).
+
+Perhaps someone else made these changes and pushed them, or they may
+have been pushed by you from a different computer.
+
+*Why should that prevent me from pushing my changes?*
+
+A good principle is that you should review and test your changes
+before you push them to another repository.  It may be OK to commit
+changes locally that don't really work or that aren't complete enough
+to test, but it's a bad idea to push anything that would cause the
+remote repository to have an untested set of changes in it.
+
+For this reason, if you change some files, someone else changes some
+others, and you both try to push them without knowing about the other
+one, Mercurial must refuse the second push -- it can't simply merge
+the changes because the result might not make any sense.
+
+Instead you must pull the other person's changes and merge them
+locally before you push.  Fortunately, this is easy to do:
+
+*1. Click Pull on the main toolbar at the top of the EasyMercurial window.*
+
+ * You should see that some changes are pulled and added to your local repository.  This will usually lead to a forked graph in the History pane, as your changes and the other user's were both started from the same parent at the same time.
+
+*2. Click Merge in the toolbar on the left.*
+
+ * Any changes that affect different files, or that affect different parts of the same file, will be merged automatically.  For changes that affect the same parts of the same file, you will be asked to choose which change to include in the merged copy.  See [[34|How do I merge my changes...]] for more about this.
+
+*3. Review or test the resulting merged version in your local working folder.*
+
+*4. Commit the merged version.*
+
+*5. Push again to the remote repository.*
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/34.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,28 @@
+{Sharing changes}
+
+Every time I push some changes, I have to type my password again
+
+When you push to a remote repository with an "https:" prefix on its
+URL, EasyMercurial's password dialog should ask whether you want to
+remember your login details.
+
+If you say yes, the details will be stored until EasyMercurial
+exits---so you won't have to enter them if you push or pull again
+while the present EasyMercurial session is running. Your password will
+be stored, encrypted with a session key, in a file which is deleted
+when EasyMercurial exits.
+
+*If this option does not appear*
+
+... and if you're using OS/X, then you need to install the PyCrypto
+library before EasyMercurial will be able to store encrypted passwords
+for you.
+
+Try running @sudo easy_install pycrypto@ in a terminal window.
+
+*Not using an https repository?*
+
+If your remote repository uses ssh or some other protocol, then
+EasyMercurial won't be able to help. You might consider using ssh
+public key authentication and an ssh agent---talk to the admin of your
+remote repository for more details.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/35.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,56 @@
+{Sharing changes}
+
+How do I use the Merge window?
+
+If you are working with other people on a project, it is likely that
+at some point you'll find that more than one of you have edited the
+same lines in the same file, in different ways.
+
+When that happens and you try to merge the two versions, EasyMercurial
+will pop up a merge window that looks a bit like this.
+
+#merge50
+
+(This is actually a separate program, not part of EasyMercurial: it's
+a merge tool called kdiff3.)
+
+This looks a bit complicated, but it is there to help you pick which
+of the changes from each of the two "rival" versions of the file you
+want to use in your merged version.
+
+ *The top middle one is _your current version_.* That's
+  the one you had before you decided to merge the other version into
+  it.
+
+ *At top right is _the version you're merging_.* That's the other
+  rival version -- the one that the other person can see in their copy
+  of the repository.
+
+ *At top left is _the common ancestor_.* That's the version that both
+  of you had, before you started editing it in different ways.
+
+ *At the bottom is the output.*
+
+Each _conflict_ (a line which you have both changed in different ways)
+is shown with *Merge Conflict* in the output at the bottom.  To sort
+out the conflicts and get the right output, you need to go through
+them one by one, for each one pressing the A, B, or C button to say
+whether for this line you want the top-left, top-middle, or top-right
+version to "win".  (You can choose more than one, if you want both
+versions of a line to appear.)
+
+Then having resolved a conflict, press the three-arrows-down toolbar
+button to go to the next one -- and when you've done them all, save
+and exit.
+
+The most important thing to remember is that _whatever appears in the
+bottom pane is what you'll get as a result_.
+
+No matter how confusing the process, just remember that if it looks
+correct in the bottom pane, it will be correct when you save and
+exit. You can even edit the file directly in the bottom pane if you
+aren't happy with the way the merge is going.
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/help/topics/40.txt	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,8 @@
+{See also}
+
+Video tutorials
+
+There are some very simple video tutorials available on the EasyMercurial website.
+
+Please see [[http://easyhg.org/videos.html]].
+
--- a/hgaction.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 HGACTION_H
-#define HGACTION_H
-
-#include <QString>
-#include <QStringList>
-
-enum HGACTIONS
-{
-    ACT_NONE,
-    ACT_TEST_HG,
-    ACT_TEST_HG_EXT,
-    ACT_QUERY_PATHS,
-    ACT_QUERY_BRANCH,
-    ACT_STAT,
-    ACT_RESOLVE_LIST,
-    ACT_QUERY_HEADS,
-    ACT_QUERY_PARENTS,
-    ACT_LOG,
-    ACT_LOG_INCREMENTAL,
-    ACT_REMOVE,
-    ACT_ADD,
-    ACT_INCOMING,
-    ACT_PUSH,
-    ACT_PULL,
-    ACT_CLONEFROMREMOTE,
-    ACT_INIT,
-    ACT_COMMIT,
-    ACT_ANNOTATE,
-    ACT_UNCOMMITTED_SUMMARY,
-    ACT_DIFF_SUMMARY,
-    ACT_FOLDERDIFF,
-    ACT_CHGSETDIFF,
-    ACT_UPDATE,
-    ACT_REVERT,
-    ACT_MERGE,
-    ACT_SERVE,
-    ACT_RESOLVE_MARK,
-    ACT_RETRY_MERGE,
-    ACT_TAG,
-    ACT_NEW_BRANCH,
-    ACT_HG_IGNORE,
-};
-
-struct HgAction
-{
-    HGACTIONS action;
-    QString workingDir;
-    QStringList params;
-    QString executable; // empty for normal Hg, but gets filled in by hgrunner
-    void *extraData;
-
-    HgAction() : action(ACT_NONE) { }
-
-    HgAction(HGACTIONS _action, QString _wd, QStringList _params) :
-        action(_action), workingDir(_wd), params(_params), extraData(0) { }
-
-    HgAction(HGACTIONS _action, QString _wd, QStringList _params, void *_d) :
-        action(_action), workingDir(_wd), params(_params), extraData(_d) { }
-
-    bool operator==(const HgAction &a) {
-        return (a.action == action && a.workingDir == workingDir &&
-                a.params == params && a.executable == executable &&
-                a.extraData == extraData);
-    }
-
-    bool shouldBeFast() const {
-        switch (action) {
-        case ACT_NONE:
-        case ACT_TEST_HG:
-        case ACT_TEST_HG_EXT:
-        case ACT_QUERY_PATHS:
-        case ACT_QUERY_BRANCH:
-        case ACT_STAT:
-        case ACT_RESOLVE_LIST:
-        case ACT_QUERY_HEADS:
-        case ACT_QUERY_PARENTS:
-        case ACT_LOG_INCREMENTAL:
-            return true;
-        default:
-            return false;
-        }
-    }
-    
-    bool mayBeInteractive() const {
-	switch (action) {
-        case ACT_TEST_HG_EXT: // so we force the module load to be tested
-	case ACT_INCOMING:
-	case ACT_PUSH:
-	case ACT_PULL:
-	case ACT_CLONEFROMREMOTE:
-	case ACT_FOLDERDIFF:
-	case ACT_CHGSETDIFF:
-	case ACT_SERVE:
-	    return true;
-	default:
-	    return false;
-	}
-    }
-};
-
-#endif
--- a/hgrunner.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,544 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "hgrunner.h"
-#include "common.h"
-#include "debug.h"
-#include "settingsdialog.h"
-
-#include <QPushButton>
-#include <QListWidget>
-#include <QDialog>
-#include <QLabel>
-#include <QVBoxLayout>
-#include <QSettings>
-#include <QInputDialog>
-#include <QTemporaryFile>
-#include <QDir>
-
-#include <iostream>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#ifndef Q_OS_WIN32
-#include <unistd.h>
-#include <termios.h>
-#include <fcntl.h>
-#endif
-
-HgRunner::HgRunner(QString myDirPath, QWidget * parent) :
-    QProgressBar(parent),
-    m_myDirPath(myDirPath)
-{
-    m_proc = 0;
-
-    // Always unbundle the extension: even if it already exists (in
-    // case we're upgrading) and even if we're not going to use it (so
-    // that it's available in case someone wants to use it later,
-    // e.g. to fix a malfunctioning setup).  But the path we actually
-    // prefer is the one in the settings first, if it exists; then the
-    // unbundled one; then anything in the path if for some reason
-    // unbundling failed
-    unbundleExtension();
-
-    setTextVisible(false);
-    setVisible(false);
-    m_isRunning = false;
-}
-
-HgRunner::~HgRunner()
-{
-    closeTerminal();
-    if (m_proc) {
-        m_proc->kill();
-        m_proc->deleteLater();
-    }
-}
-
-QString HgRunner::getUnbundledFileName()
-{
-    return SettingsDialog::getUnbundledExtensionFileName();
-}
-
-QString HgRunner::unbundleExtension()
-{
-    // Pull out the bundled Python file into a temporary file, and
-    // copy it to our known extension location, replacing the magic
-    // text NO_EASYHG_IMPORT_PATH with our installation location
-
-    QString bundled = ":easyhg.py";
-    QString unbundled = getUnbundledFileName();
-
-    QString target = QFileInfo(unbundled).path();
-    if (!QDir().mkpath(target)) {
-        DEBUG << "Failed to make unbundle path " << target << endl;
-        std::cerr << "Failed to make unbundle path " << target << std::endl;
-        return ""; 
-    }
-
-    QFile bf(bundled);
-    DEBUG << "unbundle: bundled file will be " << bundled << endl;
-    if (!bf.exists() || !bf.open(QIODevice::ReadOnly)) {
-        DEBUG << "Bundled extension is missing!" << endl;
-        return "";
-    }
-
-    QTemporaryFile tmpfile(QString("%1/easyhg.py.XXXXXX").arg(target));
-    tmpfile.setAutoRemove(false);
-    DEBUG << "unbundle: temp file will be " << tmpfile.fileName() << endl;
-    if (!tmpfile.open()) {
-        DEBUG << "Failed to open temporary file " << tmpfile.fileName() << endl;
-        std::cerr << "Failed to open temporary file " << tmpfile.fileName() << std::endl;
-        return "";
-    }
-
-    QString all = QString::fromUtf8(bf.readAll());
-    all.replace("NO_EASYHG_IMPORT_PATH", m_myDirPath);
-    tmpfile.write(all.toUtf8());
-    DEBUG << "unbundle: wrote " << all.length() << " characters" << endl;
-
-    tmpfile.close();
-
-    QFile ef(unbundled);
-    if (ef.exists()) {
-        DEBUG << "unbundle: removing old file " << unbundled << endl;
-        ef.remove();
-    }
-    DEBUG << "unbundle: renaming " << tmpfile.fileName() << " to " << unbundled << endl;
-    if (!tmpfile.rename(unbundled)) {
-        DEBUG << "Failed to move temporary file to target file " << unbundled << endl;
-        std::cerr << "Failed to move temporary file to target file " << unbundled << std::endl;
-        return "";
-    }
-    
-    DEBUG << "Unbundled extension to " << unbundled << endl;
-    return unbundled;
-}        
-
-void HgRunner::requestAction(HgAction action)
-{
-    DEBUG << "requestAction " << action.action << endl;
-    bool pushIt = true;
-    if (m_queue.empty()) {
-        if (action == m_currentAction) {
-            // this request is identical to the thing we're executing
-            DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl;
-            pushIt = false;
-        }
-    } else {
-        HgAction last = m_queue.back();
-        if (action == last) {
-            // this request is identical to the previous thing we
-            // queued which we haven't executed yet
-            DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl;
-            pushIt = false;
-        }
-    }
-    if (pushIt) m_queue.push_back(action);
-    checkQueue();
-}
-
-QString HgRunner::getHgBinaryName()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    return settings.value("hgbinary", "").toString();
-}
-
-QString HgRunner::getExtensionLocation()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    QString extpath = settings.value("extensionpath", "").toString();
-    if (extpath != "" && QFile(extpath).exists()) return extpath;
-    return "";
-}   
-
-void HgRunner::started()
-{
-    DEBUG << "started" << endl;
-    /*
-    m_proc->write("blah\n");
-    m_proc->write("blah\n");
-    m_proc -> closeWriteChannel();
-    */
-}
-
-void HgRunner::noteUsername(QString name)
-{
-    m_userName = name;
-}
-
-void HgRunner::noteRealm(QString realm)
-{
-    m_realm = realm;
-}
-
-void HgRunner::getUsername()
-{
-    if (m_ptyFile) {
-        bool ok = false;
-        QString prompt = tr("User name:");
-        if (m_realm != "") {
-            prompt = tr("User name for \"%1\":").arg(m_realm);
-        }
-        QString pwd = QInputDialog::getText
-            (qobject_cast<QWidget *>(parent()),
-            tr("Enter user name"), prompt,
-            QLineEdit::Normal, QString(), &ok);
-        if (ok) {
-            m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
-            m_ptyFile->flush();
-            return;
-        } else {
-            DEBUG << "HgRunner::getUsername: user cancelled" << endl;
-            killCurrentCommand();
-            return;
-        }
-    }
-    // user cancelled or something went wrong
-    DEBUG << "HgRunner::getUsername: something went wrong" << endl;
-    killCurrentCommand();
-}
-
-void HgRunner::getPassword()
-{
-    if (m_ptyFile) {
-        bool ok = false;
-        QString prompt = tr("Password:");
-        if (m_userName != "") {
-            if (m_realm != "") {
-                prompt = tr("Password for \"%1\" at \"%2\":")
-                         .arg(m_userName).arg(m_realm);
-            } else {
-                prompt = tr("Password for user \"%1\":")
-                         .arg(m_userName);
-            }
-        }
-        QString pwd = QInputDialog::getText
-            (qobject_cast<QWidget *>(parent()),
-            tr("Enter password"), prompt,
-             QLineEdit::Password, QString(), &ok);
-        if (ok) {
-            m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
-            m_ptyFile->flush();
-            return;
-        } else {
-            DEBUG << "HgRunner::getPassword: user cancelled" << endl;
-            killCurrentCommand();
-            return;
-        }
-    }
-    // user cancelled or something went wrong
-    DEBUG << "HgRunner::getPassword: something went wrong" << endl;
-    killCurrentCommand();
-}
-
-bool HgRunner::checkPrompts(QString chunk)
-{
-    //DEBUG << "checkPrompts: " << chunk << endl;
-
-    if (!m_currentAction.mayBeInteractive()) return false;
-
-    QString text = chunk.trimmed();
-    QString lower = text.toLower();
-    if (lower.endsWith("password:")) {
-        getPassword();
-        return true;
-    }
-    if (lower.endsWith("user:") || lower.endsWith("username:")) {
-        getUsername();
-        return true;
-    }
-    QRegExp userRe("\\buser(name)?:\\s*([^\\s]+)");
-    if (userRe.indexIn(text) >= 0) {
-        noteUsername(userRe.cap(2));
-    }
-    QRegExp realmRe("\\brealmr:\\s*([^\\s]+)");
-    if (realmRe.indexIn(text) >= 0) {
-        noteRealm(realmRe.cap(1));
-    }
-    return false;
-}
-
-void HgRunner::dataReadyStdout()
-{
-    DEBUG << "dataReadyStdout" << endl;
-    QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput());
-    if (!checkPrompts(chunk)) {
-        m_stdout += chunk;
-    }
-}
-
-void HgRunner::dataReadyStderr()
-{
-    DEBUG << "dataReadyStderr" << endl;
-    QString chunk = QString::fromUtf8(m_proc->readAllStandardError());
-    DEBUG << chunk;
-    if (!checkPrompts(chunk)) {
-        m_stderr += chunk;
-    }
-}
-
-void HgRunner::dataReadyPty()
-{
-    DEBUG << "dataReadyPty" << endl;
-    QString chunk = QString::fromUtf8(m_ptyFile->readAll());
-    DEBUG << "chunk of " << chunk.length() << " chars" << endl;
-    if (!checkPrompts(chunk)) {
-        m_stdout += chunk;
-    }
-}
-
-void HgRunner::error(QProcess::ProcessError)
-{
-    finished(-1, QProcess::CrashExit);
-}
-
-void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
-{
-    // Save the current action and reset m_currentAction before we
-    // emit a signal to mark the completion; otherwise we may be
-    // resetting the action after a slot has already tried to set it
-    // to something else to start a new action
-
-    HgAction completedAction = m_currentAction;
-
-    m_isRunning = false;
-    m_currentAction = HgAction();
-
-    //closeProcInput();
-    m_proc->deleteLater();
-    m_proc = 0;
-
-    if (completedAction.action == ACT_NONE) {
-        DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl;
-    } else {
-        if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
-            DEBUG << "HgRunner::finished: Command completed successfully"
-                  << endl;
-//            DEBUG << "stdout is " << m_stdout << endl;
-            emit commandCompleted(completedAction, m_stdout);
-        } else {
-            DEBUG << "HgRunner::finished: Command failed, exit code "
-                  << procExitCode << ", exit status " << procExitStatus
-                  << ", stderr follows" << endl;
-            DEBUG << m_stderr << endl;
-            emit commandFailed(completedAction, m_stderr);
-        }
-    }
-
-    checkQueue();
-}
-
-void HgRunner::killCurrentActions()
-{
-    m_queue.clear();
-    killCurrentCommand();
-}
-
-void HgRunner::killCurrentCommand()
-{
-    if (m_isRunning) {
-        m_currentAction.action = ACT_NONE; // so that we don't bother to notify
-        m_proc->kill();
-    }
-}
-
-void HgRunner::checkQueue()
-{
-    if (m_isRunning) {
-        return;
-    }
-    if (m_queue.empty()) {
-        hide();
-        return;
-    }
-    HgAction toRun = m_queue.front();
-    m_queue.pop_front();
-    DEBUG << "checkQueue: have action: running " << toRun.action << endl;
-    startCommand(toRun);
-}
-
-void HgRunner::startCommand(HgAction action)
-{
-    QString executable = action.executable;
-    bool interactive = false;
-    QStringList params = action.params;
-
-    if (action.workingDir.isEmpty()) {
-        // We require a working directory, never just operate in pwd
-        emit commandFailed(action, "EasyMercurial: No working directory supplied, will not run Mercurial command without one");
-        return;
-    }
-
-    QSettings settings;
-    settings.beginGroup("General");
-
-    if (executable == "") {
-        // This is a Hg command
-        executable = getHgBinaryName();
-
-        if (action.mayBeInteractive()) {
-            params.push_front("ui.interactive=true");
-            params.push_front("--config");
-
-            if (settings.value("useextension", true).toBool()) {
-                QString extpath = getExtensionLocation();
-                params.push_front(QString("extensions.easyhg=%1").arg(extpath));
-                params.push_front("--config");
-            }
-            interactive = true;
-        }            
-
-        //!!! want an option to use the mercurial_keyring extension as well
-    }
-
-    m_isRunning = true;
-    setRange(0, 0);
-    if (!action.shouldBeFast()) show();
-    m_stdout.clear();
-    m_stderr.clear();
-    m_realm = "";
-    m_userName = "";
-
-    m_proc = new QProcess;
-
-    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
-
-#ifdef Q_OS_WIN32
-    // On Win32 we like to bundle Hg and other executables with EasyHg
-    if (m_myDirPath != "") {
-        env.insert("PATH", m_myDirPath + ";" + env.value("PATH"));
-    }
-#endif
-
-#ifdef Q_OS_MAC
-    if (settings.value("python32", false).toBool()) {
-        env.insert("VERSIONER_PYTHON_PREFER_32_BIT", "1");
-    }
-#endif
-
-    env.insert("LANG", "en_US.utf8");
-    env.insert("LC_ALL", "en_US.utf8");
-    env.insert("HGPLAIN", "1");
-    m_proc->setProcessEnvironment(env);
-
-    connect(m_proc, SIGNAL(started()), this, SLOT(started()));
-    connect(m_proc, SIGNAL(error(QProcess::ProcessError)),
-            this, SLOT(error(QProcess::ProcessError)));
-    connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)),
-            this, SLOT(finished(int, QProcess::ExitStatus)));
-    connect(m_proc, SIGNAL(readyReadStandardOutput()),
-            this, SLOT(dataReadyStdout()));
-    connect(m_proc, SIGNAL(readyReadStandardError()),
-            this, SLOT(dataReadyStderr()));
-
-    m_proc->setWorkingDirectory(action.workingDir);
-
-    if (interactive) {
-        openTerminal();
-        if (m_ptySlaveFilename != "") {
-            DEBUG << "HgRunner: connecting to pseudoterminal" << endl;
-            m_proc->setStandardInputFile(m_ptySlaveFilename);
-//            m_proc->setStandardOutputFile(m_ptySlaveFilename);
-//            m_proc->setStandardErrorFile(m_ptySlaveFilename);
-        }
-    }
-
-    QString cmdline = executable;
-    foreach (QString param, params) cmdline += " " + param;
-    DEBUG << "HgRunner: starting: " << cmdline << " with cwd "
-          << action.workingDir << endl;
-
-    m_currentAction = action;
-
-    // fill these out with what we actually ran
-    m_currentAction.executable = executable;
-    m_currentAction.params = params;
-
-    DEBUG << "set current action to " << m_currentAction.action << endl;
-    
-    emit commandStarting(action);
-
-    m_proc->start(executable, params);
-}
-
-void HgRunner::closeProcInput()
-{
-    DEBUG << "closeProcInput" << endl;
-
-    m_proc->closeWriteChannel();
-}
-
-void HgRunner::openTerminal()
-{
-#ifndef Q_OS_WIN32
-    if (m_ptySlaveFilename != "") return; // already open
-    DEBUG << "HgRunner::openTerminal: trying to open new pty" << endl;
-    int master = posix_openpt(O_RDWR | O_NOCTTY);
-    if (master < 0) {
-        DEBUG << "openpt failed" << endl;
-        perror("openpt failed");
-        return;
-    }
-    struct termios t;
-    if (tcgetattr(master, &t)) {
-        DEBUG << "tcgetattr failed" << endl;
-        perror("tcgetattr failed");
-    }
-    cfmakeraw(&t);
-    if (tcsetattr(master, TCSANOW, &t)) {
-        DEBUG << "tcsetattr failed" << endl;
-        perror("tcsetattr failed");
-    }
-    if (grantpt(master)) {
-        perror("grantpt failed");
-    }
-    if (unlockpt(master)) {
-        perror("unlockpt failed");
-    }
-    char *slave = ptsname(master);
-    if (!slave) {
-        perror("ptsname failed");
-        ::close(master);
-        return;
-    }
-    m_ptyMasterFd = master;
-    m_ptyFile = new QFile();
-    connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty()));
-    if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) {
-        DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl;
-    }
-    m_ptySlaveFilename = slave;
-    DEBUG << "HgRunner::openTerminal: succeeded, slave is "
-          << m_ptySlaveFilename << endl;
-#endif
-}
-
-void HgRunner::closeTerminal()
-{
-#ifndef Q_OS_WIN32
-    if (m_ptySlaveFilename != "") {
-        delete m_ptyFile;
-        m_ptyFile = 0;
-        ::close(m_ptyMasterFd);
-        m_ptySlaveFilename = "";
-    }
-#endif
-}
--- a/hgrunner.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 HGRUNNER_H
-#define HGRUNNER_H
-
-#include "hgaction.h"
-
-#include <QProgressBar>
-#include <QProcess>
-#include <QByteArray>
-#include <QRect>
-#include <QFile>
-
-#include <deque>
-
-class HgRunner : public QProgressBar
-{
-    Q_OBJECT
-
-public:
-    HgRunner(QString myDirPath, QWidget * parent = 0);
-    ~HgRunner();
-
-    void requestAction(HgAction action);
-    void killCurrentActions(); // kill anything running; clear the queue
-
-signals:
-    void commandStarting(HgAction action);
-    void commandCompleted(HgAction action, QString stdOut);
-    void commandFailed(HgAction action, QString stdErr);
-
-private slots:
-    void started();
-    void error(QProcess::ProcessError);
-    void finished(int procExitCode, QProcess::ExitStatus procExitStatus);
-    void dataReadyStdout();
-    void dataReadyStderr();
-    void dataReadyPty();
-
-private:
-    void checkQueue();
-    void startCommand(HgAction action);
-    void closeProcInput();
-    void killCurrentCommand();
-
-    void noteUsername(QString);
-    void noteRealm(QString);
-    void getUsername();
-    void getPassword();
-    bool checkPrompts(QString);
-
-    void openTerminal();
-    void closeTerminal();
-
-    QString getHgBinaryName();
-    QString getExtensionLocation();
-
-    QString getUnbundledFileName();
-    QString unbundleExtension();
-
-    int m_ptyMasterFd;
-    int m_ptySlaveFd;
-    QString m_ptySlaveFilename;
-    QFile *m_ptyFile;
-    
-    bool m_isRunning;
-    QProcess *m_proc;
-    QString m_stdout;
-    QString m_stderr;
-
-    QString m_userName;
-    QString m_realm;
-
-    QString m_myDirPath;
-    QString m_extensionPath;
-
-    typedef std::deque<HgAction> ActionQueue;
-    ActionQueue m_queue;
-    HgAction m_currentAction;
-};
-
-#endif // HGRUNNER_H
--- a/hgtabwidget.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,259 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "hgtabwidget.h"
-#include "common.h"
-#include "filestatuswidget.h"
-#include "historywidget.h"
-
-#include <QClipboard>
-#include <QContextMenuEvent>
-#include <QApplication>
-
-#include <iostream>
-
-HgTabWidget::HgTabWidget(QWidget *parent,
-                         QString workFolderPath) :
-    QTabWidget(parent)
-{
-    // Work tab
-    m_fileStatusWidget = new FileStatusWidget;
-    m_fileStatusWidget->setLocalPath(workFolderPath);
-
-    connect(m_fileStatusWidget, SIGNAL(selectionChanged()),
-            this, SIGNAL(selectionChanged()));
-
-    connect(m_fileStatusWidget, SIGNAL(showAllChanged(bool)),
-            this, SIGNAL(showAllChanged(bool)));
-
-    connect(m_fileStatusWidget, SIGNAL(annotateFiles(QStringList)),
-            this, SIGNAL(annotateFiles(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(diffFiles(QStringList)),
-            this, SIGNAL(diffFiles(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(commitFiles(QStringList)),
-            this, SIGNAL(commitFiles(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(revertFiles(QStringList)),
-            this, SIGNAL(revertFiles(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(addFiles(QStringList)),
-            this, SIGNAL(addFiles(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(removeFiles(QStringList)),
-            this, SIGNAL(removeFiles(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(redoFileMerges(QStringList)),
-            this, SIGNAL(redoFileMerges(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(markFilesResolved(QStringList)),
-            this, SIGNAL(markFilesResolved(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(ignoreFiles(QStringList)),
-            this, SIGNAL(ignoreFiles(QStringList)));
-
-    connect(m_fileStatusWidget, SIGNAL(unIgnoreFiles(QStringList)),
-            this, SIGNAL(unIgnoreFiles(QStringList)));
-
-    addTab(m_fileStatusWidget, tr("My work"));
-
-    // History graph tab
-    m_historyWidget = new HistoryWidget;
-    addTab(m_historyWidget, tr("History"));
-
-    connect(m_historyWidget, SIGNAL(commit()),
-            this, SIGNAL(commit()));
-    
-    connect(m_historyWidget, SIGNAL(revert()),
-            this, SIGNAL(revert()));
-    
-    connect(m_historyWidget, SIGNAL(showSummary()),
-            this, SIGNAL(showSummary()));
-    
-    connect(m_historyWidget, SIGNAL(newBranch()),
-            this, SIGNAL(newBranch()));
-    
-    connect(m_historyWidget, SIGNAL(noBranch()),
-            this, SIGNAL(noBranch()));
-    
-    connect(m_historyWidget, SIGNAL(diffWorkingFolder()),
-            this, SIGNAL(diffWorkingFolder()));
-
-    connect(m_historyWidget, SIGNAL(showWork()),
-            this, SLOT(showWorkTab()));
-
-    connect(m_historyWidget, SIGNAL(updateTo(QString)),
-            this, SIGNAL(updateTo(QString)));
-
-    connect(m_historyWidget, SIGNAL(diffToCurrent(QString)),
-            this, SIGNAL(diffToCurrent(QString)));
-
-    connect(m_historyWidget, SIGNAL(diffToParent(QString, QString)),
-            this, SIGNAL(diffToParent(QString, QString)));
-
-    connect(m_historyWidget, SIGNAL(showSummary(Changeset *)),
-            this, SIGNAL(showSummary(Changeset *)));
-
-    connect(m_historyWidget, SIGNAL(mergeFrom(QString)),
-            this, SIGNAL(mergeFrom(QString)));
-
-    connect(m_historyWidget, SIGNAL(newBranch(QString)),
-            this, SIGNAL(newBranch(QString)));
-
-    connect(m_historyWidget, SIGNAL(tag(QString)),
-            this, SIGNAL(tag(QString)));
-}
-
-void HgTabWidget::clearSelections()
-{
-    m_fileStatusWidget->clearSelections();
-}
-
-void HgTabWidget::setCurrent(QStringList ids, QString branch)
-{
-    bool showUncommitted = haveChangesToCommit();
-    m_historyWidget->setCurrent(ids, branch, showUncommitted);
-}
-
-void HgTabWidget::updateFileStates()
-{
-    m_fileStatusWidget->updateWidgets();
-}
-
-void HgTabWidget::updateHistory()
-{
-    m_historyWidget->update();
-}
-
-bool HgTabWidget::canDiff() const
-{
-    return canRevert();
-}
-
-bool HgTabWidget::canCommit() const
-{
-    if (!m_fileStatusWidget->haveChangesToCommit()) return false;
-    if (!m_fileStatusWidget->getAllUnresolvedFiles().empty()) return false;
-    return true;
-}
-
-bool HgTabWidget::canRevert() const
-{
-    // Not the same as canCommit() -- we can revert (and diff)
-    // unresolved files, but we can't commit them
-    if (!m_fileStatusWidget->haveChangesToCommit() &&
-        m_fileStatusWidget->getAllUnresolvedFiles().empty()) return false;
-    return true;
-}
-
-bool HgTabWidget::canAdd() const
-{
-    // Permit this only when work tab is visible
-    if (currentIndex() != 0) return false;
-
-    QStringList addable = m_fileStatusWidget->getSelectedAddableFiles();
-    if (addable.empty()) return false;
-
-    QStringList removable = m_fileStatusWidget->getSelectedRemovableFiles();
-    if (!removable.empty()) return false;
-
-    return true;
-}
-
-bool HgTabWidget::canRemove() const
-{
-    // Permit this only when work tab is visible
-    if (currentIndex() != 0) return false;
-
-    if (m_fileStatusWidget->getSelectedRemovableFiles().empty()) return false;
-    if (!m_fileStatusWidget->getSelectedAddableFiles().empty()) return false;
-    return true;
-}
-
-bool HgTabWidget::canResolve() const
-{
-    return !m_fileStatusWidget->getAllUnresolvedFiles().empty();
-}
-
-bool HgTabWidget::haveChangesToCommit() const
-{
-    return m_fileStatusWidget->haveChangesToCommit();
-}
-
-QStringList HgTabWidget::getAllCommittableFiles() const
-{
-    return m_fileStatusWidget->getAllCommittableFiles();
-}
-
-QStringList HgTabWidget::getAllRevertableFiles() const
-{
-    return m_fileStatusWidget->getAllRevertableFiles();
-}
-
-QStringList HgTabWidget::getSelectedAddableFiles() const
-{
-    return m_fileStatusWidget->getSelectedAddableFiles();
-}
-
-QStringList HgTabWidget::getSelectedRemovableFiles() const
-{
-    return m_fileStatusWidget->getSelectedRemovableFiles();
-}
-
-QStringList HgTabWidget::getAllUnresolvedFiles() const
-{
-    return m_fileStatusWidget->getAllUnresolvedFiles();
-}
-
-void HgTabWidget::updateWorkFolderFileList(QString fileList)
-{
-    m_fileStates.parseStates(fileList);
-    m_fileStatusWidget->setFileStates(m_fileStates);
-}
-
-void HgTabWidget::setNewLog(QString hgLogList)
-{
-    m_historyWidget->parseNewLog(hgLogList);
-    if (m_historyWidget->haveNewItems()) {
-        showHistoryTab();
-    }
-}
-
-void HgTabWidget::addIncrementalLog(QString hgLogList)
-{
-    m_historyWidget->parseIncrementalLog(hgLogList);
-    if (m_historyWidget->haveNewItems()) {
-        showHistoryTab();
-    }
-}
-
-void HgTabWidget::setLocalPath(QString workFolderPath)
-{
-    m_fileStatusWidget->setLocalPath(workFolderPath);
-}
-
-void HgTabWidget::showWorkTab()
-{
-    setCurrentWidget(m_fileStatusWidget);
-}
-
-void HgTabWidget::showHistoryTab()
-{
-    setCurrentWidget(m_historyWidget);
-}
-
--- a/hgtabwidget.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 HGTABWIDGET_H
-#define HGTABWIDGET_H
-
-#include "changeset.h"
-#include "common.h"
-#include "filestates.h"
-
-#include <QMenu>
-#include <QListWidget>
-#include <QGroupBox>
-#include <QVBoxLayout>
-#include <QCheckBox>
-#include <QLabel>
-#include <QTabWidget>
-
-class FileStatusWidget;
-class HistoryWidget;
-
-class HgTabWidget: public QTabWidget
-{
-    Q_OBJECT
-
-public:
-    HgTabWidget(QWidget *parent, QString workFolderPath);
-
-    void updateWorkFolderFileList(QString fileList);
-
-    void setNewLog(QString hgLogList);
-    void addIncrementalLog(QString hgLogList);
-
-    void setLocalPath(QString workFolderPath);
-
-    void setCurrent(QStringList ids, QString branch);
-
-    void updateFileStates();
-    void updateHistory();
-
-    FileStates getFileStates() { return m_fileStates; }
-
-    bool canDiff() const;
-    bool canCommit() const;
-    bool canRevert() const;
-    bool canAdd() const;
-    bool canRemove() const;
-    bool canResolve() const;
-    bool haveChangesToCommit() const;
-
-    QStringList getAllCommittableFiles() const;
-    QStringList getAllRevertableFiles() const;
-    QStringList getAllUnresolvedFiles() const;
-
-    QStringList getSelectedAddableFiles() const;
-    QStringList getSelectedRemovableFiles() const;
-
-signals:
-    void selectionChanged();
-    void showAllChanged(bool);
-
-    void commit();
-    void revert();
-    void diffWorkingFolder();
-    void showSummary();
-    void newBranch();
-    void noBranch();
-
-    void updateTo(QString id);
-    void diffToParent(QString id, QString parent);
-    void showSummary(Changeset *);
-    void diffToCurrent(QString id);
-    void mergeFrom(QString id);
-    void newBranch(QString id);
-    void tag(QString id);
-
-    void annotateFiles(QStringList);
-    void diffFiles(QStringList);
-    void commitFiles(QStringList);
-    void revertFiles(QStringList);
-    void addFiles(QStringList);
-    void removeFiles(QStringList);
-    void redoFileMerges(QStringList);
-    void markFilesResolved(QStringList);
-    void ignoreFiles(QStringList);
-    void unIgnoreFiles(QStringList);
-
-public slots:
-    void clearSelections();
-    void showWorkTab();
-    void showHistoryTab();
-
-private:
-    FileStatusWidget *m_fileStatusWidget;
-    HistoryWidget *m_historyWidget;
-    FileStates m_fileStates;
-
-    Changesets parseChangeSets(QString changeSetsStr);
-};
-
-#endif // HGTABWIDGET_H
--- a/historywidget.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,298 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "historywidget.h"
-
-#include "changesetscene.h"
-#include "panned.h"
-#include "panner.h"
-#include "grapher.h"
-#include "debug.h"
-#include "uncommitteditem.h"
-
-#include <iostream>
-
-#include <QGridLayout>
-
-HistoryWidget::HistoryWidget() :
-    m_showUncommitted(false),
-    m_refreshNeeded(false)
-{
-    m_panned = new Panned;
-    m_panner = new Panner;
-
-    m_panned->setDragMode(QGraphicsView::ScrollHandDrag);
-    m_panned->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
-
-    QGridLayout *layout = new QGridLayout;
-    layout->addWidget(m_panned, 0, 0);
-    layout->addWidget(m_panner, 0, 1);
-    m_panner->setMaximumWidth(80);
-    m_panner->connectToPanned(m_panned);
-
-    setLayout(layout);
-}
-
-HistoryWidget::~HistoryWidget()
-{
-    clearChangesets();
-}
-
-QGraphicsScene *HistoryWidget::scene()
-{
-    return m_panned->scene();
-}
-
-void HistoryWidget::clearChangesets()
-{
-    foreach (Changeset *cs, m_changesets) delete cs;
-    m_changesets.clear();
-}
-
-void HistoryWidget::setCurrent(QStringList ids, QString branch,
-                               bool showUncommitted)
-{
-    if (m_currentIds == ids &&
-        m_currentBranch == branch &&
-        m_showUncommitted == showUncommitted) return;
-
-    DEBUG << "HistoryWidget::setCurrent: " << ids.size() << " ids, "
-          << "showUncommitted: " << showUncommitted << endl;
-
-    m_currentIds.clear();
-    m_currentBranch = branch;
-    m_showUncommitted = showUncommitted;
-
-    if (ids.empty()) return;
-
-    foreach (QString id, ids) {
-        m_currentIds.push_back(id);
-    }
-
-    m_refreshNeeded = true;
-}
-    
-void HistoryWidget::parseNewLog(QString log)
-{
-    DEBUG << "HistoryWidget::parseNewLog: log has " << log.length() << " chars" << endl;
-    Changesets csets = Changeset::parseChangesets(log);
-    DEBUG << "HistoryWidget::parseNewLog: log has " << csets.size() << " changesets" << endl;
-    replaceChangesets(csets);
-    m_refreshNeeded = true;
-}
-    
-void HistoryWidget::parseIncrementalLog(QString log)
-{
-    DEBUG << "HistoryWidget::parseIncrementalLog: log has " << log.length() << " chars" << endl;
-    Changesets csets = Changeset::parseChangesets(log);
-    DEBUG << "HistoryWidget::parseIncrementalLog: log has " << csets.size() << " changesets" << endl;
-    if (!csets.empty()) {
-        addChangesets(csets);
-    }
-    m_refreshNeeded = true;
-}
-
-void HistoryWidget::replaceChangesets(Changesets csets)
-{
-    QSet<QString> oldIds;
-    foreach (Changeset *cs, m_changesets) {
-        oldIds.insert(cs->id());
-    }
-
-    QSet<QString> newIds;
-    foreach (Changeset *cs, csets) {
-        if (!oldIds.contains(cs->id())) {
-            newIds.insert(cs->id());
-        }
-    }
-
-    if (newIds.size() == csets.size()) {
-        // completely new set, unrelated to the old: don't mark new
-        m_newIds.clear();
-    } else {
-        m_newIds = newIds;
-    }
-
-    clearChangesets();
-    m_changesets = csets;
-}
-
-void HistoryWidget::addChangesets(Changesets csets)
-{
-    m_newIds.clear();
-
-    if (csets.empty()) return;
-
-    foreach (Changeset *cs, csets) {
-        m_newIds.insert(cs->id());
-    }
-
-    DEBUG << "addChangesets: " << csets.size() << " new changesets have ("
-          << m_changesets.size() << " already)" << endl;
-
-    csets << m_changesets;
-    m_changesets = csets;
-}
-
-void HistoryWidget::update()
-{
-    if (m_refreshNeeded) {
-        layoutAll();
-    }
-}
-
-void HistoryWidget::layoutAll()
-{
-    m_refreshNeeded = false;
-
-    setChangesetParents();
-
-    ChangesetScene *scene = new ChangesetScene();
-    ChangesetItem *tipItem = 0;
-
-    QGraphicsScene *oldScene = m_panned->scene();
-
-    m_panned->setScene(0);
-    m_panner->setScene(0);
-
-    delete oldScene;
-
-    QGraphicsItem *toFocus = 0;
-
-    if (!m_changesets.empty()) {
-	Grapher g(scene);
-	try {
-	    g.layout(m_changesets,
-                     m_showUncommitted ? m_currentIds : QStringList(),
-                     m_currentBranch);
-	} catch (std::string s) {
-	    std::cerr << "Internal error: Layout failed: " << s << std::endl;
-	}
-        toFocus = g.getUncommittedItem();
-        if (!toFocus) {
-            toFocus = g.getItemFor(m_changesets[0]);
-        }
-    }
-
-    m_panned->setScene(scene);
-    m_panner->setScene(scene);
-
-    updateNewAndCurrentItems();
-
-    if (toFocus) {
-        toFocus->ensureVisible();
-    }
-
-    connectSceneSignals();
-}
-
-void HistoryWidget::setChangesetParents()
-{
-    for (int i = 0; i < m_changesets.size(); ++i) {
-        Changeset *cs = m_changesets[i];
-        // Need to reset this, as Grapher::layout will recalculate it
-        // and we don't want to end up with twice the children for
-        // each parent...
-        cs->setChildren(QStringList());
-    }
-    for (int i = 0; i+1 < m_changesets.size(); ++i) {
-        Changeset *cs = m_changesets[i];
-        if (cs->parents().empty()) {
-            QStringList list;
-            list.push_back(m_changesets[i+1]->id());
-            cs->setParents(list);
-        }
-    }
-}
-
-void HistoryWidget::updateNewAndCurrentItems()
-{
-    QGraphicsScene *scene = m_panned->scene();
-    if (!scene) return;
-
-    QList<QGraphicsItem *> items = scene->items();
-    foreach (QGraphicsItem *it, items) {
-
-        ChangesetItem *csit = dynamic_cast<ChangesetItem *>(it);
-        if (!csit) continue;
-
-        QString id = csit->getChangeset()->id();
-
-        bool current = m_currentIds.contains(id);
-        if (current) {
-            DEBUG << "id " << id << " is current" << endl;
-        }
-        bool newid = m_newIds.contains(id);
-        if (newid) {
-            DEBUG << "id " << id << " is new" << endl;
-        }
-
-        if (csit->isCurrent() != current || csit->isNew() != newid) {
-            csit->setCurrent(current);
-            csit->setNew(newid);
-            csit->update();
-        }
-    }
-}
-
-void HistoryWidget::connectSceneSignals()
-{
-    ChangesetScene *scene = qobject_cast<ChangesetScene *>(m_panned->scene());
-    if (!scene) return;
-    
-    connect(scene, SIGNAL(commit()),
-            this, SIGNAL(commit()));
-    
-    connect(scene, SIGNAL(revert()),
-            this, SIGNAL(revert()));
-    
-    connect(scene, SIGNAL(diffWorkingFolder()),
-            this, SIGNAL(diffWorkingFolder()));
-
-    connect(scene, SIGNAL(showSummary()),
-            this, SIGNAL(showSummary()));
-
-    connect(scene, SIGNAL(showWork()),
-            this, SIGNAL(showWork()));
-
-    connect(scene, SIGNAL(newBranch()),
-            this, SIGNAL(newBranch()));
-
-    connect(scene, SIGNAL(noBranch()),
-            this, SIGNAL(noBranch()));
-    
-    connect(scene, SIGNAL(updateTo(QString)),
-            this, SIGNAL(updateTo(QString)));
-
-    connect(scene, SIGNAL(diffToCurrent(QString)),
-            this, SIGNAL(diffToCurrent(QString)));
-
-    connect(scene, SIGNAL(diffToParent(QString, QString)),
-            this, SIGNAL(diffToParent(QString, QString)));
-
-    connect(scene, SIGNAL(showSummary(Changeset *)),
-            this, SIGNAL(showSummary(Changeset *)));
-
-    connect(scene, SIGNAL(mergeFrom(QString)),
-            this, SIGNAL(mergeFrom(QString)));
-
-    connect(scene, SIGNAL(newBranch(QString)),
-            this, SIGNAL(newBranch(QString)));
-
-    connect(scene, SIGNAL(tag(QString)),
-            this, SIGNAL(tag(QString)));
-}
--- a/historywidget.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 HISTORYWIDGET_H
-#define HISTORYWIDGET_H
-
-#include "changeset.h"
-
-#include <QWidget>
-#include <QSet>
-
-class Panned;
-class Panner;
-class UncommittedItem;
-class QGraphicsScene;
-
-class HistoryWidget : public QWidget
-{
-    Q_OBJECT
-
-public:
-    HistoryWidget();
-    virtual ~HistoryWidget();
-
-    void setCurrent(QStringList ids, QString branch, bool showUncommitted);
-
-    void parseNewLog(QString log);
-    void parseIncrementalLog(QString log);
-
-    bool haveNewItems() const { return !m_newIds.empty(); }
-
-    void update();
-
-signals:
-    void commit();
-    void revert();
-    void diffWorkingFolder();
-    void showSummary();
-    void showWork();
-    void newBranch();
-    void noBranch();
-
-    void updateTo(QString id);
-    void diffToParent(QString id, QString parent);
-    void showSummary(Changeset *);
-    void diffToCurrent(QString id);
-    void mergeFrom(QString id);
-    void newBranch(QString id);
-    void tag(QString id);
-    
-private:
-    Changesets m_changesets;
-    QStringList m_currentIds;
-    QString m_currentBranch;
-    QSet<QString> m_newIds;
-    bool m_showUncommitted;
-    bool m_refreshNeeded;
-
-    Panned *m_panned;
-    Panner *m_panner;
-
-    QGraphicsScene *scene();
-    void clearChangesets();
-    void replaceChangesets(Changesets);
-    void addChangesets(Changesets);
-    void layoutAll();
-    void setChangesetParents();
-    void updateNewAndCurrentItems();
-    void connectSceneSignals();
-};
-
-#endif
Binary file images/back.png has changed
Binary file images/cancel-small.png has changed
Binary file images/crappy-icon-design.xcf has changed
Binary file images/crappy-icon.png has changed
Binary file images/easyhg-icon.png has changed
Binary file images/forward.png has changed
Binary file images/home.png has changed
Binary file images/icon/128/easyhg-icon.png has changed
Binary file images/icon/16/easyhg-icon.png has changed
Binary file images/icon/192/easyhg-icon.png has changed
Binary file images/icon/22/easyhg-icon.png has changed
Binary file images/icon/24/easyhg-icon.png has changed
Binary file images/icon/256/easyhg-icon.png has changed
Binary file images/icon/32/easyhg-icon.png has changed
Binary file images/icon/48/easyhg-icon.png has changed
Binary file images/icon/64/easyhg-icon.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/images/icon/scalable/easyhg-icon.svg	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="64px"
+   height="64px"
+   id="svg2985"
+   version="1.1"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="test-new-icon.svg"
+   inkscape:export-filename="/work/easyhg/test-new-icon-256.png"
+   inkscape:export-xdpi="360"
+   inkscape:export-ydpi="360">
+  <defs
+     id="defs2987">
+    <linearGradient
+       id="linearGradient3793">
+      <stop
+         id="stop3795"
+         offset="0"
+         style="stop-color:#b4b4b4;stop-opacity:1;" />
+      <stop
+         id="stop3797"
+         offset="1"
+         style="stop-color:#ffffff;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3785">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3787" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3789" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3793"
+       id="linearGradient3791"
+       x1="12.287697"
+       y1="34.172951"
+       x2="51.994736"
+       y2="34.172951"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.7781746"
+     inkscape:cx="32.570025"
+     inkscape:cy="32.596202"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:document-units="px"
+     inkscape:grid-bbox="true"
+     inkscape:snap-global="true"
+     inkscape:window-width="1030"
+     inkscape:window-height="759"
+     inkscape:window-x="775"
+     inkscape:window-y="345"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2997"
+       empspacing="2"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="false"
+       dotted="false" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata2990">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.08466768;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="path2995"
+       sodipodi:cx="32.141216"
+       sodipodi:cy="34.172951"
+       sodipodi:rx="19.156166"
+       sodipodi:ry="19.284731"
+       d="m 51.297382,34.172951 a 19.156166,19.284731 0 1 1 -38.312332,0 19.156166,19.284731 0 1 1 38.312332,0 z"
+       transform="matrix(1.017949,0,0,1.0111627,-2.2181191,1.4455869)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:url(#linearGradient3791);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.01503563;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="path2995-1"
+       sodipodi:cx="32.141216"
+       sodipodi:cy="34.172951"
+       sodipodi:rx="19.156166"
+       sodipodi:ry="19.284731"
+       d="m 51.297382,34.172951 a 19.156166,19.284731 0 1 1 -38.312332,0 19.156166,19.284731 0 1 1 38.312332,0 z"
+       transform="matrix(0,0.80913895,-0.80374471,0,57.778828,9.9932907)" />
+    <path
+       sodipodi:type="star"
+       style="fill:#acacac;fill-opacity:1;stroke:#000000;stroke-width:1.60000002;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="path3805"
+       sodipodi:sides="5"
+       sodipodi:cx="36"
+       sodipodi:cy="6"
+       sodipodi:r1="10.630146"
+       sodipodi:r2="4.3201118"
+       sodipodi:arg1="0.85196633"
+       sodipodi:arg2="1.4802849"
+       inkscape:flatsided="false"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="m 43,14 -6.609514,-3.697572 -5.835819,4.827104 1.474148,-7.4286344 -6.394216,-4.0585369 7.520588,-0.8935761 1.883976,-7.3354175 3.173831,6.8763737 7.558577,-0.4750005 -5.559052,5.1434088 z"
+       inkscape:transform-center-x="-0.073740988"
+       inkscape:transform-center-y="-0.94470063"
+       transform="matrix(0.99362658,0.11272187,-0.11272187,0.99362658,12.137363,6.1314664)" />
+  </g>
+</svg>
Binary file images/star.png has changed
--- a/incomingdialog.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 "incomingdialog.h"
-#include "changeset.h"
-#include "common.h"
-
-#include <QScrollArea>
-#include <QApplication>
-#include <QDialogButtonBox>
-#include <QLabel>
-#include <QGridLayout>
-#include <QStyle>
-
-IncomingDialog::IncomingDialog(QWidget *w, QString text) :
-    QDialog(w)
-{
-    QString head;
-    QString body;
-    bool scroll;
-
-    Changesets csets = Changeset::parseChangesets(text);
-    if (csets.empty()) {
-	head = tr("No changes waiting to pull");
-	if (text.trimmed() != "") {
-	    body = QString("<p>%1</p><code>%2</code>")
-		.arg(tr("The command output was:"))
-		.arg(xmlEncode(text).replace("\n", "<br>"));
-	} else {
-            body = tr("<qt>Your local repository already contains all changes found in the remote repository.</qt>");
-        }
-	scroll = false;
-    } else {
-        head = tr("There are %n change(s) ready to pull", "", csets.size());
-	foreach (Changeset *cs, csets) {
-	    body += cs->formatHtml() + "<p>";
-	    delete cs;
-	}
-	scroll = true;
-    }
-
-    QGridLayout *layout = new QGridLayout;
-    setLayout(layout);
-
-    QLabel *info = new QLabel;
-    QStyle *style = qApp->style();
-    int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this);
-    info->setPixmap(style->standardIcon(QStyle::SP_MessageBoxInformation, 0, this)
-		    .pixmap(iconSize, iconSize));
-    layout->addWidget(info, 0, 0, 2, 1);
-
-    QLabel *headLabel = new QLabel(QString("<qt><h3>%1</h3></qt>").arg(head));
-    layout->addWidget(headLabel, 0, 1);
-
-    QLabel *textLabel = new QLabel(body);
-    if (csets.empty()) textLabel->setWordWrap(true);
-
-    if (scroll) {
-	QScrollArea *sa = new QScrollArea;
-	layout->addWidget(sa, 1, 1);
-	layout->setRowStretch(1, 20);
-	sa->setWidget(textLabel);
-    } else {
-	layout->addWidget(textLabel, 1, 1);
-    }
-
-    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
-    connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
-    layout->addWidget(bb, 2, 0, 1, 2);
-
-    layout->setColumnStretch(1, 20);
-    setMinimumWidth(400);
-}
-
-    
--- a/incomingdialog.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 INCOMING_DIALOG_H
-#define INCOMING_DIALOG_H
-
-#include <QDialog>
-
-class IncomingDialog : public QDialog
-{
-    Q_OBJECT
-
-public:
-    IncomingDialog(QWidget *parent, QString incoming);
-};
-
-#endif
--- a/logparser.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "logparser.h"
-
-#include "debug.h"
-
-#include <QStringList>
-#include <QRegExp>
-
-LogParser::LogParser(QString text, QString separator) :
-    m_text(text), m_sep(separator)
-{
-    m_text.replace("\r\n", "\n");
-}
-
-QStringList LogParser::split()
-{
-    return m_text.split("\n\n", QString::SkipEmptyParts);
-}
-
-LogList LogParser::parse()
-{
-    LogList results;
-    QRegExp re(QString("^(\\w+)\\s*%1\\s+([^\\s].*)$").arg(m_sep));
-    QStringList entries = split();
-    foreach (QString entry, entries) {
-        LogEntry dictionary;
-        QStringList lines = entry.split('\n');
-        foreach (QString line, lines) {
-            if (re.indexIn(line) == 0) {
-                QString key = re.cap(1);
-                QString value = re.cap(2);
-                dictionary[key.trimmed()] = value;
-            }
-        }
-        results.push_back(dictionary);
-    }
-    return results;
-}
--- a/logparser.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 LOGPARSER_H
-#define LOGPARSER_H
-
-#include <QObject>
-#include <QString>
-#include <QList>
-#include <QMap>
-
-typedef QMap<QString, QString> LogEntry;
-typedef QList<LogEntry> LogList;
-
-class LogParser : public QObject
-{
-public:
-    LogParser(QString text, QString separator = ":");
-
-    QStringList split();
-    LogList parse();
-
-private:
-    QString m_text;
-    QString m_sep;
-};
-
-#endif // LOGPARSER_H
--- a/main.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "mainwindow.h"
-#include "common.h"
-#include "debug.h"
-
-#include <QApplication>
-#include <QTranslator>
-#include <QDir>
-
-int main(int argc, char *argv[])
-{
-    QApplication app(argc, argv);
-
-    QApplication::setOrganizationName("easymercurial");
-    QApplication::setOrganizationDomain("easymercurial.org");
-    QApplication::setApplicationName(QApplication::tr("EasyMercurial"));
-
-    // Lose our controlling terminal (so we can provide a new pty to
-    // capture password requests)
-    loseControllingTerminal();
-
-    installSignalHandlers();
-
-    QTranslator translator;
-    QString language = QLocale::system().name();
-    QString trname = QString("easyhg_%1").arg(language);
-    translator.load(trname, ":");
-    app.installTranslator(&translator);
-
-    QStringList args = app.arguments();
-
-    QString myDirPath = QFileInfo(QDir::current().absoluteFilePath(args[0]))
-        .canonicalPath();
-
-    MainWindow mainWin(myDirPath);
-    mainWin.show();
-
-    if (args.size() == 2) {
-        QString path = args[1];
-        DEBUG << "Opening " << args[1] << endl;
-        if (QDir(path).exists()) {
-            path = QDir(path).canonicalPath();
-            mainWin.open(path);
-        }
-    }
-
-    return app.exec();
-}
--- a/mainwindow.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2623 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 <QStringList>
-#include <QDir>
-#include <QNetworkInterface>
-#include <QHostAddress>
-#include <QHostInfo>
-#include <QDesktopServices>
-#include <QStatusBar>
-#include <QMessageBox>
-#include <QMenuBar>
-#include <QApplication>
-#include <QToolBar>
-#include <QToolButton>
-#include <QSettings>
-#include <QInputDialog>
-#include <QRegExp>
-#include <QShortcut>
-#include <QUrl>
-#include <QTimer>
-
-#include "mainwindow.h"
-#include "multichoicedialog.h"
-#include "startupdialog.h"
-#include "colourset.h"
-#include "debug.h"
-#include "logparser.h"
-#include "confirmcommentdialog.h"
-#include "incomingdialog.h"
-#include "settingsdialog.h"
-#include "moreinformationdialog.h"
-#include "annotatedialog.h"
-#include "version.h"
-#include "workstatuswidget.h"
-
-
-MainWindow::MainWindow(QString myDirPath) :
-    m_myDirPath(myDirPath),
-    m_fsWatcherGeneralTimer(0),
-    m_fsWatcherRestoreTimer(0),
-    m_fsWatcherSuspended(false)
-{
-    setWindowIcon(QIcon(":images/easyhg-icon.png"));
-
-    QString wndTitle;
-
-    m_showAllFiles = false;
-
-    m_fsWatcher = 0;
-    m_commitsSincePush = 0;
-    m_shouldHgStat = true;
-
-    createActions();
-    createMenus();
-    createToolBars();
-    createStatusBar();
-
-    m_runner = new HgRunner(m_myDirPath, this);
-    connect(m_runner, SIGNAL(commandStarting(HgAction)),
-            this, SLOT(commandStarting(HgAction)));
-    connect(m_runner, SIGNAL(commandCompleted(HgAction, QString)),
-            this, SLOT(commandCompleted(HgAction, QString)));
-    connect(m_runner, SIGNAL(commandFailed(HgAction, QString)),
-            this, SLOT(commandFailed(HgAction, QString)));
-    statusBar()->addPermanentWidget(m_runner);
-
-    setWindowTitle(tr("EasyMercurial"));
-
-    m_remoteRepoPath = "";
-    m_workFolderPath = "";
-
-    readSettings();
-
-    m_justMerged = false;
-
-    QWidget *central = new QWidget(this);
-    setCentralWidget(central);
-
-    QGridLayout *cl = new QGridLayout(central);
-    int row = 0;
-
-#ifndef Q_OS_MAC
-    cl->setMargin(0);
-#endif
-
-    m_workStatus = new WorkStatusWidget(this);
-    cl->addWidget(m_workStatus, row++, 0);
-
-    m_hgTabs = new HgTabWidget(central, m_workFolderPath);
-    connectTabsSignals();
-
-    cl->addWidget(m_hgTabs, row++, 0);
-
-    connect(m_hgTabs, SIGNAL(selectionChanged()),
-            this, SLOT(enableDisableActions()));
-    connect(m_hgTabs, SIGNAL(showAllChanged(bool)),
-            this, SLOT(showAllChanged(bool)));
-
-    setUnifiedTitleAndToolBarOnMac(true);
-    connectActions();
-    clearState();
-    enableDisableActions();
-
-    if (m_firstStart) {
-        startupDialog();
-    }
-
-    SettingsDialog::findDefaultLocations(m_myDirPath);
-
-    ColourSet *cs = ColourSet::instance();
-    cs->clearDefaultNames();
-    cs->addDefaultName("");
-    cs->addDefaultName("default");
-    cs->addDefaultName(getUserInfo());
-
-    hgTest();
-}
-
-
-void MainWindow::closeEvent(QCloseEvent *)
-{
-    writeSettings();
-    delete m_fsWatcher;
-}
-
-
-QString MainWindow::getUserInfo() const
-{
-    QSettings settings;
-    settings.beginGroup("User Information");
-    QString name = settings.value("name", getUserRealName()).toString();
-    QString email = settings.value("email", "").toString();
-
-    QString identifier;
-
-    if (email != "") {
-	identifier = QString("%1 <%2>").arg(name).arg(email);
-    } else {
-	identifier = name;
-    }
-
-    return identifier;
-}
-
-void MainWindow::about()
-{
-   QMessageBox::about(this, tr("About EasyMercurial"),
-                      tr("<qt><h2>EasyMercurial v%1</h2>"
-#ifdef Q_OS_MAC
-                         "<font size=-1>"
-#endif
-                         "<p>EasyMercurial is a simple user interface for the "
-                         "Mercurial</a> version control system.</p>"
-                         "<h4>Credits and Copyright</h4>"
-                         "<p>Development carried out by Chris Cannam for "
-                         "SoundSoftware.ac.uk at the Centre for Digital Music, "
-                         "Queen Mary, University of London.</p>"
-                         "<p>EasyMercurial is based on HgExplorer by "
-                         "Jari Korhonen, with thanks.</p>"
-                         "<p style=\"margin-left: 2em;\">"
-                         "Copyright &copy; 2011 Queen Mary, University of London.<br>"
-                         "Copyright &copy; 2010 Jari Korhonen.<br>"
-                         "Copyright &copy; 2011 Chris Cannam."
-                         "</p>"
-                         "<p style=\"margin-left: 2em;\">"
-                         "This program requires Mercurial, by Matt Mackall and others.<br>"
-                         "This program uses Qt by Nokia.<br>"
-                         "This program uses Nuvola icons by David Vignoni.<br>"
-                         "This program may use KDiff3 by Joachim Eibl.<br>"
-                         "This program may use PyQt by River Bank Computing.<br>"
-                         "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others."
-                         "</p>"
-                         "<h4>License</h4>"
-                         "<p>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.</p>"
-#ifdef Q_OS_MAC
-                         "</font>"
-#endif
-                          ).arg(EASYHG_VERSION));
-}
-
-void MainWindow::clearSelections()
-{
-    m_hgTabs->clearSelections();
-}
-
-void MainWindow::showAllChanged(bool s)
-{
-    m_showAllFiles = s;
-    hgQueryPaths();
-}
-
-void MainWindow::hgRefresh()
-{
-    clearState();
-    hgQueryPaths();
-}
-
-void MainWindow::hgTest()
-{
-    QStringList params;
-    //!!! should we test version output? Really we want at least 1.7.x
-    //!!! for options such as merge --tool
-    params << "--version";
-    m_runner->requestAction(HgAction(ACT_TEST_HG, m_myDirPath, params));
-}
-
-void MainWindow::hgTestExtension()
-{
-    QStringList params;
-    params << "--version";
-    m_runner->requestAction(HgAction(ACT_TEST_HG_EXT, m_myDirPath, params));
-}
-
-void MainWindow::hgStat()
-{
-    QStringList params;
-
-    if (m_showAllFiles) {
-        params << "stat" << "-A";
-    } else {
-        params << "stat" << "-ardum";
-    }
-
-    m_lastStatOutput = "";
-
-    m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params));
-}
-
-void MainWindow::hgQueryPaths()
-{
-    // Quickest is to just read the file
-
-    QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc");
-
-    QString path;
-
-    if (hgrc.exists()) {
-        QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat);
-        s.beginGroup("paths");
-        path = s.value("default").toString();
-    }
-
-    m_remoteRepoPath = path;
-
-    // We have to do this here, because commandCompleted won't be called
-    MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
-    MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
-    updateWorkFolderAndRepoNames();
-    
-    hgQueryBranch();
-    return;
-
-/* The classic method!
-
-    QStringList params;
-    params << "paths";
-    m_runner->requestAction(HgAction(ACT_QUERY_PATHS, m_workFolderPath, params));
-*/
-}
-
-void MainWindow::hgQueryBranch()
-{
-    // Quickest is to just read the file
-
-    QFile hgbr(m_workFolderPath + "/.hg/branch");
-
-    QString br = "default";
-
-    if (hgbr.exists() && hgbr.open(QFile::ReadOnly)) {
-        QByteArray ba = hgbr.readLine();
-        br = QString::fromUtf8(ba).trimmed();
-    }
-    
-    m_currentBranch = br;
-    
-    // We have to do this here, because commandCompleted won't be called
-    hgStat();
-    return;
-
-/* The classic method!
-
-    QStringList params;
-    params << "branch";
-    m_runner->requestAction(HgAction(ACT_QUERY_BRANCH, m_workFolderPath, params));
-*/
-}
-
-void MainWindow::hgQueryHeads()
-{
-    QStringList params;
-    // On empty repos, "hg heads" will fail -- we don't care about
-    // that.  Use --closed option so as to include closed branches;
-    // otherwise we'll be stuck if the user updates into one, and our
-    // incremental log will end up with spurious stuff in it because
-    // we won't be pruning at the ends of closed branches
-    params << "heads" << "--closed";
-    m_runner->requestAction(HgAction(ACT_QUERY_HEADS, m_workFolderPath, params));
-}
-
-void MainWindow::hgLog()
-{
-    QStringList params;
-    params << "log";
-    params << "--template";
-    params << Changeset::getLogTemplate();
-    
-    m_runner->requestAction(HgAction(ACT_LOG, m_workFolderPath, params));
-}
-
-void MainWindow::hgLogIncremental(QStringList prune)
-{
-    // Sometimes we can be called with prune empty -- it represents
-    // the current heads, but if we have none already and for some
-    // reason are being prompted for an incremental update, we may run
-    // into trouble.  In that case, make this a full log instead
-
-    if (prune.empty()) {
-        hgLog();
-        return;
-    }
-
-    QStringList params;
-    params << "log";
-
-    foreach (QString p, prune) {
-        params << "--prune" << Changeset::hashOf(p);
-    }
-        
-    params << "--template";
-    params << Changeset::getLogTemplate();
-    
-    m_runner->requestAction(HgAction(ACT_LOG_INCREMENTAL, m_workFolderPath, params));
-}
-
-void MainWindow::hgQueryParents()
-{
-    QStringList params;
-    params << "parents";
-    m_runner->requestAction(HgAction(ACT_QUERY_PARENTS, m_workFolderPath, params));
-}
-
-void MainWindow::hgAnnotateFiles(QStringList files)
-{
-    QStringList params;
-    
-    if (!files.isEmpty()) {
-        params << "annotate" << "-udqc" << "--" << files;
-        m_runner->requestAction(HgAction(ACT_ANNOTATE, m_workFolderPath, params));
-    }
-}
-
-void MainWindow::hgResolveList()
-{
-    QStringList params;
-
-    params << "resolve" << "--list";
-    m_runner->requestAction(HgAction(ACT_RESOLVE_LIST, m_workFolderPath, params));
-}
-
-void MainWindow::hgAdd()
-{
-    // hgExplorer permitted adding "all" files -- I'm not sure
-    // that one is a good idea, let's require the user to select
-
-    hgAddFiles(m_hgTabs->getSelectedAddableFiles());
-}
-
-void MainWindow::hgAddFiles(QStringList files)
-{
-    QStringList params;
-
-    if (!files.empty()) {
-        params << "add" << "--" << files;
-        m_runner->requestAction(HgAction(ACT_ADD, m_workFolderPath, params));
-    }
-}
-
-void MainWindow::hgRemove()
-{
-    hgRemoveFiles(m_hgTabs->getSelectedRemovableFiles());
-}
-
-void MainWindow::hgRemoveFiles(QStringList files)
-{
-    QStringList params;
-
-    if (!files.empty()) {
-        params << "remove" << "--after" << "--force" << "--" << files;
-        m_runner->requestAction(HgAction(ACT_REMOVE, m_workFolderPath, params));
-    }
-}
-
-void MainWindow::hgCommit()
-{
-    hgCommitFiles(QStringList());
-}
-
-void MainWindow::hgCommitFiles(QStringList files)
-{
-    QStringList params;
-    QString comment;
-
-    if (m_justMerged) {
-        comment = m_mergeCommitComment;
-    }
-
-    QStringList allFiles = m_hgTabs->getAllCommittableFiles();
-    QStringList reportFiles = files;
-    if (reportFiles.empty()) {
-        reportFiles = allFiles;
-    }
-
-    QString subsetNote;
-    if (reportFiles != allFiles) {
-        subsetNote = tr("<p><b>Note:</b> you are committing only the files you have selected, not all of the files that have been changed!");
-    }
-    
-    QString cf(tr("Commit files"));
-
-    QString branchText;
-    if (m_currentBranch == "" || m_currentBranch == "default") {
-        branchText = tr("the default branch");
-    } else {
-        branchText = tr("branch \"%1\"").arg(m_currentBranch);
-    }
-
-    if (ConfirmCommentDialog::confirmAndGetLongComment
-        (this,
-         cf,
-         tr("<h3>%1</h3><p>%2%3").arg(cf)
-         .arg(tr("You are about to commit the following files to %1:").arg(branchText))
-         .arg(subsetNote),
-         tr("<h3>%1</h3><p>%2%3").arg(cf)
-         .arg(tr("You are about to commit %n file(s) to %1.", "", reportFiles.size()).arg(branchText))
-         .arg(subsetNote),
-         reportFiles,
-         comment,
-         tr("Commit"))) {
-
-        if (!m_justMerged && !files.empty()) {
-            // User wants to commit selected file(s) (and this is not
-            // merge commit, which would fail if we selected files)
-            params << "commit" << "--message" << comment
-                   << "--user" << getUserInfo() << "--" << files;
-        } else {
-            // Commit all changes
-            params << "commit" << "--message" << comment
-                   << "--user" << getUserInfo();
-        }
-        
-        m_runner->requestAction(HgAction(ACT_COMMIT, m_workFolderPath, params));
-        m_mergeCommitComment = "";
-    }
-}
-
-QString MainWindow::filterTag(QString tag)
-{
-    for(int i = 0; i < tag.size(); i++) {
-        if (tag[i].isLower() || tag[i].isUpper() ||
-            tag[i].isDigit() || (tag[i] == QChar('.'))) {
-            //ok
-        } else {
-            tag[i] = QChar('_');
-        }
-    }
-    return tag;
-}
-
-
-void MainWindow::hgNewBranch()
-{
-    QStringList params;
-    QString branch;
-
-    if (ConfirmCommentDialog::confirmAndGetShortComment
-        (this,
-         tr("New Branch"),
-         tr("Enter new branch name:"),
-         branch,
-         tr("Start Branch"))) {
-        if (!branch.isEmpty()) {//!!! do something better if it is empty
-
-            params << "branch" << filterTag(branch);
-            m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
-        }
-    }
-}
-
-void MainWindow::hgNoBranch()
-{
-    if (m_currentParents.empty()) return;
-
-    QString parentBranch = m_currentParents[0]->branch();
-    if (parentBranch == "") parentBranch = "default";
-
-    QStringList params;
-    params << "branch" << parentBranch;
-    m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
-}
-
-void MainWindow::hgTag(QString id)
-{
-    QStringList params;
-    QString tag;
-
-    if (ConfirmCommentDialog::confirmAndGetShortComment
-        (this,
-         tr("Tag"),
-         tr("Enter tag:"),
-         tag,
-         tr("Add Tag"))) {
-        if (!tag.isEmpty()) {//!!! do something better if it is empty
-
-            params << "tag" << "--user" << getUserInfo();
-            params << "--rev" << Changeset::hashOf(id) << filterTag(tag);
-            
-            m_runner->requestAction(HgAction(ACT_TAG, m_workFolderPath, params));
-        }
-    }
-}
-
-void MainWindow::hgIgnore()
-{
-    QString hgIgnorePath;
-    QStringList params;
-    
-    hgIgnorePath = m_workFolderPath;
-    hgIgnorePath += "/.hgignore";
-
-    if (!QDir(m_workFolderPath).exists()) return;
-    QFile f(hgIgnorePath);
-    if (!f.exists()) {
-        f.open(QFile::WriteOnly);
-        QTextStream *ts = new QTextStream(&f);
-        *ts << "syntax: glob\n";
-        delete ts;
-        f.close();
-    }
-    
-    params << hgIgnorePath;
-    
-    QString editor = getEditorBinaryName();
-
-    if (editor == "") {
-        DEBUG << "Failed to find a text editor" << endl;
-        //!!! visible error!
-        return;
-    }
-
-    HgAction action(ACT_HG_IGNORE, m_workFolderPath, params);
-    action.executable = editor;
-
-    m_runner->requestAction(action);
-}
-
-void MainWindow::hgIgnoreFiles(QStringList files)
-{
-    //!!! not implemented yet
-    DEBUG << "MainWindow::hgIgnoreFiles: Not implemented" << endl;
-}
-
-void MainWindow::hgUnIgnoreFiles(QStringList files)
-{
-    //!!! not implemented yet
-    DEBUG << "MainWindow::hgUnIgnoreFiles: Not implemented" << endl;
-}
-
-QString MainWindow::getDiffBinaryName()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    return settings.value("extdiffbinary", "").toString();
-}
-
-QString MainWindow::getMergeBinaryName()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    return settings.value("mergebinary", "").toString();
-}
-
-QString MainWindow::getEditorBinaryName()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    return settings.value("editorbinary", "").toString();
-}
-
-void MainWindow::hgShowSummary()
-{
-    QStringList params;
-    
-    params << "diff" << "--stat";
-
-    m_runner->requestAction(HgAction(ACT_UNCOMMITTED_SUMMARY, m_workFolderPath, params));
-}
-
-void MainWindow::hgFolderDiff()
-{
-    hgDiffFiles(QStringList());
-}
-
-void MainWindow::hgDiffFiles(QStringList files)
-{
-    QString diff = getDiffBinaryName();
-    if (diff == "") return;
-
-    QStringList params;
-
-    // Diff parent against working folder (folder diff)
-
-    params << "--config" << "extensions.extdiff=" << "extdiff";
-    params << "--program" << diff;
-
-    params << "--" << files; // may be none: whole dir
-
-    m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params));
-}
-
-void MainWindow::hgDiffToCurrent(QString id)
-{
-    QString diff = getDiffBinaryName();
-    if (diff == "") return;
-
-    QStringList params;
-
-    // Diff given revision against working folder
-
-    params << "--config" << "extensions.extdiff=" << "extdiff";
-    params << "--program" << diff;
-    params << "--rev" << Changeset::hashOf(id);
-
-    m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params));
-}
-
-void MainWindow::hgDiffToParent(QString child, QString parent)
-{
-    QString diff = getDiffBinaryName();
-    if (diff == "") return;
-
-    QStringList params;
-
-    // Diff given revision against parent revision
-
-    params << "--config" << "extensions.extdiff=" << "extdiff";
-    params << "--program" << diff;
-    params << "--rev" << Changeset::hashOf(parent)
-           << "--rev" << Changeset::hashOf(child);
-
-    m_runner->requestAction(HgAction(ACT_CHGSETDIFF, m_workFolderPath, params));
-}
-    
-
-void MainWindow::hgShowSummaryFor(Changeset *cs)
-{
-    QStringList params;
-
-    // This will pick a default parent if there is more than one
-    // (whereas with diff we need to supply one).  But it does need a
-    // bit more parsing
-    params << "log" << "--stat" << "--rev" << Changeset::hashOf(cs->id());
-
-    m_runner->requestAction(HgAction(ACT_DIFF_SUMMARY, m_workFolderPath,
-                                     params, cs));
-}
-
-
-void MainWindow::hgUpdate()
-{
-    QStringList params;
-
-    params << "update";
-    
-    m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params));
-}
-
-
-void MainWindow::hgUpdateToRev(QString id)
-{
-    QStringList params;
-
-    params << "update" << "--rev" << Changeset::hashOf(id) << "--check";
-
-    m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params));
-}
-
-
-void MainWindow::hgRevert()
-{
-    hgRevertFiles(QStringList());
-}
-
-void MainWindow::hgRevertFiles(QStringList files)
-{
-    QStringList params;
-    QString comment;
-    bool all = false;
-
-    QStringList allFiles = m_hgTabs->getAllRevertableFiles();
-    if (files.empty() || files == allFiles) {
-        files = allFiles;
-        all = true;
-    }
-    
-    QString subsetNote;
-    if (!all) {
-        subsetNote = tr("<p><b>Note:</b> you are reverting only the files you have selected, not all of the files that have been changed!");
-    }
-
-    QString rf(tr("Revert files"));
-
-    // Set up params before asking for confirmation, because there is
-    // a failure case here that we would need to report on early
-
-    DEBUG << "hgRevert: m_justMerged = " << m_justMerged << ", m_mergeTargetRevision = " << m_mergeTargetRevision << endl;
-
-    if (m_justMerged) {
-
-        // This is a little fiddly.  The proper way to "revert" the
-        // whole of an uncommitted merge is with "hg update --clean ."
-        // But if the user has selected only some files, we're sort of
-        // promising to revert only those, which means we need to
-        // specify which parent to revert to.  We can only do that if
-        // we have a record of it, which we do if you just did the
-        // merge from within easyhg but don't if you've exited and
-        // restarted, or changed repository, since then.  Hmmm.
-
-        if (all) {
-            params << "update" << "--clean" << ".";
-        } else {
-            if (m_mergeTargetRevision != "") {
-                params << "revert" << "--rev"
-                       << Changeset::hashOf(m_mergeTargetRevision)
-                       << "--" << files;
-            } else {
-                QMessageBox::information
-                    (this, tr("Unable to revert"),
-                     tr("<qt><b>Sorry, unable to revert these files</b><br><br>EasyMercurial can only revert a subset of files during a merge if it still has a record of which parent was the original merge target; that information is no longer available.<br><br>This is a limitation of EasyMercurial.  Consider reverting all files, or using hg revert with a specific revision at the command-line instead.</qt>"));
-                return;
-            }
-        }
-    } else {
-        params << "revert" << "--" << files;
-    }
-
-    if (ConfirmCommentDialog::confirmDangerousFilesAction
-        (this,
-         rf,
-         tr("<h3>%1</h3><p>%2%3").arg(rf)
-         .arg(tr("You are about to <b>revert</b> the following files to their previous committed state.<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed."))
-         .arg(subsetNote),
-         tr("<h3>%1</h3><p>%2%3").arg(rf)
-         .arg(tr("You are about to <b>revert</b> %n file(s).<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed.", "", files.size()))
-         .arg(subsetNote),
-         files,
-         tr("Revert"))) {
-
-        m_lastRevertedFiles = files;
-        
-        m_runner->requestAction(HgAction(ACT_REVERT, m_workFolderPath, params));
-    }
-}
-
-
-void MainWindow::hgMarkFilesResolved(QStringList files)
-{
-    QStringList params;
-
-    params << "resolve" << "--mark";
-
-    if (files.empty()) {
-        params << "--all";
-    } else {
-        params << "--" << files;
-    }
-
-    m_runner->requestAction(HgAction(ACT_RESOLVE_MARK, m_workFolderPath, params));
-}
-
-
-void MainWindow::hgRedoMerge()
-{
-    hgRedoFileMerges(QStringList());
-}
-
-
-void MainWindow::hgRedoFileMerges(QStringList files)
-{
-    QStringList params;
-
-    params << "resolve";
-
-    QString merge = getMergeBinaryName();
-    if (merge != "") {
-        params << "--tool" << merge;
-    }
-
-    if (files.empty()) {
-        params << "--all";
-    } else {
-        params << "--" << files;
-    }
-
-    if (m_currentParents.size() == 1) {
-        m_mergeTargetRevision = m_currentParents[0]->id();
-    }
-
-    m_runner->requestAction(HgAction(ACT_RETRY_MERGE, m_workFolderPath, params));
-
-    m_mergeCommitComment = tr("Merge");
-}
-    
-
-void MainWindow::hgMerge()
-{
-    if (m_hgTabs->canResolve()) {
-        hgRedoMerge();
-        return;
-    }
-
-    QStringList params;
-
-    params << "merge";
-
-    QString merge = getMergeBinaryName();
-    if (merge != "") {
-        params << "--tool" << merge;
-    }
-
-    if (m_currentParents.size() == 1) {
-        m_mergeTargetRevision = m_currentParents[0]->id();
-    }
-
-    m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params));
-
-    m_mergeCommitComment = tr("Merge");
-}
-
-
-void MainWindow::hgMergeFrom(QString id)
-{
-    QStringList params;
-
-    params << "merge";
-    params << "--rev" << Changeset::hashOf(id);
-
-    QString merge = getMergeBinaryName();
-    if (merge != "") {
-        params << "--tool" << merge;
-    }
-    
-    if (m_currentParents.size() == 1) {
-        m_mergeTargetRevision = m_currentParents[0]->id();
-    }
-
-    m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params));
-
-    m_mergeCommitComment = "";
-
-    foreach (Changeset *cs, m_currentHeads) {
-        if (cs->id() == id && !cs->isOnBranch(m_currentBranch)) {
-            if (cs->branch() == "" || cs->branch() == "default") {
-                m_mergeCommitComment = tr("Merge from the default branch");
-            } else {
-                m_mergeCommitComment = tr("Merge from branch \"%1\"").arg(cs->branch());
-            }
-        }
-    }
-
-    if (m_mergeCommitComment == "") {
-        m_mergeCommitComment = tr("Merge from %1").arg(id);
-    }
-}
-
-
-void MainWindow::hgCloneFromRemote()
-{
-    QStringList params;
-
-    if (!QDir(m_workFolderPath).exists()) {
-        if (!QDir().mkpath(m_workFolderPath)) {
-            DEBUG << "hgCloneFromRemote: Failed to create target path "
-                  << m_workFolderPath << endl;
-            //!!! report error
-            return;
-        }
-    }
-
-    params << "clone" << m_remoteRepoPath << m_workFolderPath;
-    
-    updateWorkFolderAndRepoNames();
-    m_hgTabs->updateWorkFolderFileList("");
-
-    m_runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, m_workFolderPath, params));
-}
-
-void MainWindow::hgInit()
-{
-    QStringList params;
-
-    params << "init";
-    params << m_workFolderPath;
-
-    m_runner->requestAction(HgAction(ACT_INIT, m_workFolderPath, params));
-}
-
-void MainWindow::hgIncoming()
-{
-    QStringList params;
-
-    params << "incoming" << "--newest-first" << m_remoteRepoPath;
-    params << "--template" << Changeset::getLogTemplate();
-
-    m_runner->requestAction(HgAction(ACT_INCOMING, m_workFolderPath, params));
-}
-
-void MainWindow::hgPull()
-{
-    if (ConfirmCommentDialog::confirm
-        (this, tr("Confirm pull"),
-         tr("<qt><h3>Pull from remote repository?</h3></qt>"),
-         tr("<qt><p>You are about to pull changes from the remote repository at <code>%1</code>.</p></qt>").arg(xmlEncode(m_remoteRepoPath)),
-         tr("Pull"))) {
-
-        QStringList params;
-        params << "pull" << m_remoteRepoPath;
-        m_runner->requestAction(HgAction(ACT_PULL, m_workFolderPath, params));
-    }
-}
-
-void MainWindow::hgPush()
-{
-    if (ConfirmCommentDialog::confirm
-        (this, tr("Confirm push"),
-         tr("<qt><h3>Push to remote repository?</h3></qt>"),
-         tr("<qt><p>You are about to push your changes to the remote repository at <code>%1</code>.</p></qt>").arg(xmlEncode(m_remoteRepoPath)),
-         tr("Push"))) {
-
-        QStringList params;
-        params << "push" << "--new-branch" << m_remoteRepoPath;
-        m_runner->requestAction(HgAction(ACT_PUSH, m_workFolderPath, params));
-    }
-}
-
-QStringList MainWindow::listAllUpIpV4Addresses()
-{
-    QStringList ret;
-    QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
-
-    for (int i = 0; i < ifaces.count(); i++) {
-        QNetworkInterface iface = ifaces.at(i);
-        if (iface.flags().testFlag(QNetworkInterface::IsUp)
-            && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
-            for (int j=0; j<iface.addressEntries().count(); j++) {
-                QHostAddress tmp = iface.addressEntries().at(j).ip();
-                if (QAbstractSocket::IPv4Protocol == tmp.protocol()) {
-                    ret.push_back(tmp.toString());
-                }
-            }
-        }
-    }
-    return ret;
-}
-
-void MainWindow::clearState()
-{
-    DEBUG << "MainWindow::clearState" << endl;
-    foreach (Changeset *cs, m_currentParents) delete cs;
-    m_currentParents.clear();
-    foreach (Changeset *cs, m_currentHeads) delete cs;
-    m_currentHeads.clear();
-    m_currentBranch = "";
-    m_lastStatOutput = "";
-    m_lastRevertedFiles.clear();
-    m_mergeTargetRevision = "";
-    m_mergeCommitComment = "";
-    m_stateUnknown = true;
-    m_needNewLog = true;
-    if (m_fsWatcher) {
-        delete m_fsWatcherGeneralTimer;
-        m_fsWatcherGeneralTimer = 0;
-        delete m_fsWatcherRestoreTimer;
-        m_fsWatcherRestoreTimer = 0;
-        delete m_fsWatcher;
-        m_fsWatcher = 0;
-    }
-}
-
-void MainWindow::hgServe()
-{
-    QStringList params;
-    QString msg;
-
-    QStringList addrs = listAllUpIpV4Addresses();
-
-    if (addrs.empty()) {
-        QMessageBox::critical
-            (this, tr("Serve"), tr("Failed to identify an active IPv4 address"));
-        return;
-    }
-
-    //!!! should find available port as well
-
-    QTextStream ts(&msg);
-    ts << QString("<qt><p>%1</p>")
-        .arg(tr("Running temporary server at %n address(es):", "", addrs.size()));
-    foreach (QString addr, addrs) {
-        ts << QString("<pre>&nbsp;&nbsp;http://%1:8000</pre>").arg(xmlEncode(addr));
-    }
-    ts << tr("<p>Press Close to stop the server and return.</p>");
-    ts.flush();
-             
-    params << "serve";
-
-    m_runner->requestAction(HgAction(ACT_SERVE, m_workFolderPath, params));
-    
-    QMessageBox::information(this, tr("Serve"), msg, QMessageBox::Close);
-
-    m_runner->killCurrentActions();
-}
-
-void MainWindow::startupDialog()
-{
-    StartupDialog *dlg = new StartupDialog(this);
-    if (dlg->exec()) m_firstStart = false;
-}
-
-void MainWindow::open()
-{
-    bool done = false;
-
-    while (!done) {
-
-        MultiChoiceDialog *d = new MultiChoiceDialog
-                               (tr("Open Repository"),
-                                tr("<qt><big>What would you like to open?</big></qt>"),
-                                tr("https://code.soundsoftware.ac.uk/projects/easyhg/wiki/HelpOpenDialog"),
-                                this);
-
-        d->addChoice("local",
-                     tr("<qt><center><img src=\":images/hglogo-64.png\"><br>Local repository</center></qt>"),
-                     tr("Open an existing local Mercurial repository."),
-                     MultiChoiceDialog::DirectoryArg);
-
-        d->addChoice("init",
-                     tr("<qt><center><img src=\":images/hdd_unmount-64.png\"><br>File folder</center></qt>"),
-                     tr("Open a local folder, by creating a Mercurial repository in it."),
-                     MultiChoiceDialog::DirectoryArg);
-
-        d->addChoice("remote",
-                     tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"),
-                     tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."),
-                     MultiChoiceDialog::UrlToDirectoryArg);
-
-        QSettings settings;
-        settings.beginGroup("General");
-        QString lastChoice = settings.value("lastopentype", "local").toString();
-        if (lastChoice != "local" &&
-            lastChoice != "remote" &&
-            lastChoice != "init") {
-            lastChoice = "local";
-        }
-
-        d->setCurrentChoice(lastChoice);
-
-        if (d->exec() == QDialog::Accepted) {
-
-            QString choice = d->getCurrentChoice();
-            settings.setValue("lastopentype", choice);
-
-            QString arg = d->getArgument().trimmed();
-
-            bool result = false;
-
-            if (choice == "local") {
-                result = openLocal(arg);
-            } else if (choice == "remote") {
-                result = openRemote(arg, d->getAdditionalArgument().trimmed());
-            } else if (choice == "init") {
-                result = openInit(arg);
-            }
-
-            if (result) {
-                enableDisableActions();
-                clearState();
-                hgQueryPaths();
-                done = true;
-            }
-
-        } else {
-
-            // cancelled
-            done = true;
-        }
-
-        delete d;
-    }
-}
-
-void MainWindow::changeRemoteRepo()
-{
-    // This will involve rewriting the local .hgrc
-
-    QDir hgDir(m_workFolderPath + "/.hg");
-    if (!hgDir.exists()) {
-        //!!! visible error!
-        return;
-    }
-
-    QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc");
-    if (hgrc.exists() && !hgrc.isWritable()) {
-        //!!! visible error!
-        return;
-    }
-
-    MultiChoiceDialog *d = new MultiChoiceDialog
-        (tr("Change Remote Location"),
-         tr("<qt><big>Change the remote location</big></qt>"),
-         "",
-         this);
-
-    d->addChoice("remote",
-                 tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"),
-                 tr("Provide a new URL to use for push and pull actions from the current local repository."),
-                 MultiChoiceDialog::UrlArg);
-
-    if (d->exec() == QDialog::Accepted) {
-
-        // New block to ensure QSettings is deleted before
-        // hgQueryPaths called.  NB use of absoluteFilePath instead of
-        // canonicalFilePath, which would fail if the file did not yet
-        // exist
-
-        {
-            QSettings s(hgrc.absoluteFilePath(), QSettings::IniFormat);
-            s.beginGroup("paths");
-            s.setValue("default", d->getArgument());
-        }
-
-        m_stateUnknown = true;
-        hgQueryPaths();
-    }
-
-    delete d;
-}
-
-void MainWindow::open(QString local)
-{
-    if (openLocal(local)) {
-        enableDisableActions();
-        clearState();
-        hgQueryPaths();
-    }
-}
-
-bool MainWindow::complainAboutFilePath(QString arg)
-{    
-    QMessageBox::critical
-        (this, tr("File chosen"),
-         tr("<qt><b>Folder required</b><br><br>You asked to open \"%1\".<br>This is a file; to open a repository, you need to choose a folder.</qt>").arg(xmlEncode(arg)));
-    return false;
-}
-
-bool MainWindow::askAboutUnknownFolder(QString arg)
-{    
-    bool result = (QMessageBox::question
-                   (this, tr("Path does not exist"),
-                    tr("<qt><b>Path does not exist: create it?</b><br><br>You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.<br><br>Would you like to create the parent folder as well?</qt>").arg(xmlEncode(arg)),
-                    QMessageBox::Ok | QMessageBox::Cancel,
-                    QMessageBox::Cancel)
-                   == QMessageBox::Ok);
-    if (result) {
-        QDir dir(arg);
-        dir.cdUp();
-        if (!dir.mkpath(dir.absolutePath())) {
-            QMessageBox::critical
-                (this, tr("Failed to create folder"),
-                 tr("<qt><b>Failed to create folder</b><br><br>Sorry, the path for the parent folder \"%1\" could not be created.</qt>").arg(dir.absolutePath()));
-            return false;
-        }
-        return true;
-    }
-    return false;
-}
-
-bool MainWindow::complainAboutUnknownFolder(QString arg)
-{    
-    QMessageBox::critical
-        (this, tr("Folder does not exist"),
-         tr("<qt><b>Folder does not exist</b><br><br>You asked to open \"%1\".<br>This folder does not exist, and it cannot be created because its parent does not exist either.</qt>").arg(xmlEncode(arg)));
-    return false;
-}
-
-bool MainWindow::complainAboutInitInRepo(QString arg)
-{
-    QMessageBox::critical
-        (this, tr("Path is in existing repository"),
-         tr("<qt><b>Path is in an existing repository</b><br><br>You asked to initialise a repository at \"%1\".<br>This path is already inside an existing repository.</qt>").arg(xmlEncode(arg)));
-    return false;
-}
-
-bool MainWindow::complainAboutInitFile(QString arg)
-{
-    QMessageBox::critical
-        (this, tr("Path is a file"),
-         tr("<qt><b>Path is a file</b><br><br>You asked to initialise a repository at \"%1\".<br>This is an existing file; it is only possible to initialise in folders.</qt>").arg(xmlEncode(arg)));
-    return false;
-}
-
-bool MainWindow::complainAboutCloneToExisting(QString arg)
-{
-    QMessageBox::critical
-        (this, tr("Path is in existing repository"),
-         tr("<qt><b>Local path is in an existing repository</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is already inside an existing repository.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg)));
-    return false;
-}
-
-bool MainWindow::complainAboutCloneToFile(QString arg)
-{
-    QMessageBox::critical
-        (this, tr("Path is a file"),
-         tr("<qt><b>Local path is a file</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is an existing file.<br>Please provide a new folder name for the local repository.</qt>").arg(xmlEncode(arg)));
-    return false;
-}
-
-QString MainWindow::complainAboutCloneToExistingFolder(QString arg, QString remote)
-{
-    // If the directory "arg" exists but "arg" plus the last path
-    // component of "remote" does not, then offer the latter as an
-    // alternative path
-
-    QString offer;
-
-    QDir d(arg);
-    if (d.exists()) {
-        if (QRegExp("^\\w+://").indexIn(remote) >= 0) {
-            QString rpath = QUrl(remote).path();
-            if (rpath != "") {
-                rpath = QDir(rpath).dirName();
-                if (rpath != "" && !d.exists(rpath)) {
-                    offer = d.filePath(rpath);
-                }
-            }
-        }
-    }
-
-    if (offer != "") {
-        bool result = (QMessageBox::question
-                       (this, tr("Folder exists"),
-                        tr("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.<br><br>Would you like to create the new folder \"%2\" instead?</qt>")
-                        .arg(xmlEncode(arg)).arg(xmlEncode(offer)),
-                        QMessageBox::Ok | QMessageBox::Cancel,
-                        QMessageBox::Cancel)
-                       == QMessageBox::Ok);
-        if (result) return offer;
-        else return "";
-    }
-
-    QMessageBox::critical
-        (this, tr("Folder exists"),
-         tr("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this file or folder already exists and so cannot be cloned to.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg)));
-    return "";
-}
-
-bool MainWindow::askToOpenParentRepo(QString arg, QString parent)
-{
-    return (QMessageBox::question
-            (this, tr("Path is inside a repository"),
-             tr("<qt><b>Open the repository that contains this path?</b><br><br>You asked to open \"%1\".<br>This is not the root folder of a repository.<br>But it is inside a repository, whose root is at \"%2\". <br><br>Would you like to open that repository instead?</qt>")
-             .arg(xmlEncode(arg)).arg(xmlEncode(parent)),
-             QMessageBox::Ok | QMessageBox::Cancel,
-             QMessageBox::Ok)
-            == QMessageBox::Ok);
-}
-
-bool MainWindow::askToInitExisting(QString arg)
-{
-    return (QMessageBox::question
-            (this, tr("Folder has no repository"),
-             tr("<qt><b>Initialise a repository here?</b><br><br>You asked to open \"%1\".<br>This folder does not contain a Mercurial repository.<br><br>Would you like to initialise a repository here?</qt>")
-             .arg(xmlEncode(arg)),
-             QMessageBox::Ok | QMessageBox::Cancel,
-             QMessageBox::Ok)
-            == QMessageBox::Ok);
-}
-
-bool MainWindow::askToInitNew(QString arg)
-{
-    return (QMessageBox::question
-            (this, tr("Folder does not exist"),
-             tr("<qt><b>Initialise a new repository?</b><br><br>You asked to open \"%1\".<br>This folder does not yet exist.<br><br>Would you like to create the folder and initialise a new empty repository in it?</qt>")
-             .arg(xmlEncode(arg)),
-             QMessageBox::Ok | QMessageBox::Cancel,
-             QMessageBox::Ok)
-            == QMessageBox::Ok);
-}
-
-bool MainWindow::askToOpenInsteadOfInit(QString arg)
-{
-    return (QMessageBox::question
-            (this, tr("Repository exists"),
-             tr("<qt><b>Open existing repository?</b><br><br>You asked to initialise a new repository at \"%1\".<br>This folder already contains a repository.  Would you like to open it?</qt>")
-             .arg(xmlEncode(arg)),
-             QMessageBox::Ok | QMessageBox::Cancel,
-             QMessageBox::Ok)
-            == QMessageBox::Ok);
-}
-
-bool MainWindow::openLocal(QString local)
-{
-    DEBUG << "open " << local << endl;
-
-    FolderStatus status = getFolderStatus(local);
-    QString containing = getContainingRepoFolder(local);
-
-    switch (status) {
-
-    case FolderHasRepo:
-        // fine
-        break;
-
-    case FolderExists:
-        if (containing != "") {
-            if (!askToOpenParentRepo(local, containing)) return false;
-            local = containing;
-        } else {
-            //!!! No -- this is likely to happen far more by accident
-            // than because the user actually wanted to init something.
-            // Don't ask, just politely reject.
-            if (!askToInitExisting(local)) return false;
-            return openInit(local);
-        }
-        break;
-
-    case FolderParentExists:
-        if (containing != "") {
-            if (!askToOpenParentRepo(local, containing)) return false;
-            local = containing;
-        } else {
-            if (!askToInitNew(local)) return false;
-            return openInit(local);
-        }
-        break;
-
-    case FolderUnknown:
-        if (containing != "") {
-            if (!askToOpenParentRepo(local, containing)) return false;
-            local = containing;
-        } else {
-            return complainAboutUnknownFolder(local);
-        }
-        break;
-        
-    case FolderIsFile:
-        return complainAboutFilePath(local);
-    }
-
-    m_workFolderPath = local;
-    m_remoteRepoPath = "";
-    return true;
-}    
-
-bool MainWindow::openRemote(QString remote, QString local)
-{
-    DEBUG << "clone " << remote << " to " << local << endl;
-
-    FolderStatus status = getFolderStatus(local);
-    QString containing = getContainingRepoFolder(local);
-
-    DEBUG << "status = " << status << ", containing = " << containing << endl;
-
-    if (status == FolderHasRepo || containing != "") {
-        return complainAboutCloneToExisting(local);
-    }
-
-    if (status == FolderIsFile) {
-        return complainAboutCloneToFile(local);
-    }
-
-    if (status == FolderUnknown) {
-        if (!askAboutUnknownFolder(local)) {
-            return false;
-        }
-    }
-
-    if (status == FolderExists) {
-        local = complainAboutCloneToExistingFolder(local, remote);
-        if (local == "") return false;
-    }
-
-    m_workFolderPath = local;
-    m_remoteRepoPath = remote;
-    hgCloneFromRemote();
-
-    return true;
-}
-
-bool MainWindow::openInit(QString local)
-{
-    DEBUG << "openInit " << local << endl;
-
-    FolderStatus status = getFolderStatus(local);
-    QString containing = getContainingRepoFolder(local);
-
-    DEBUG << "status = " << status << ", containing = " << containing << endl;
-
-    if (status == FolderHasRepo) {
-        if (!askToOpenInsteadOfInit(local)) return false;
-    }
-
-    if (containing != "") {
-        return complainAboutInitInRepo(local);
-    }
-
-    if (status == FolderIsFile) {
-        return complainAboutInitFile(local);
-    }
-
-    if (status == FolderUnknown) {
-        return complainAboutUnknownFolder(local);
-    }
-
-    m_workFolderPath = local;
-    m_remoteRepoPath = "";
-    hgInit();
-    return true;
-}
-
-void MainWindow::settings()
-{
-    SettingsDialog *settingsDlg = new SettingsDialog(this);
-    settingsDlg->exec();
-
-    if (settingsDlg->presentationChanged()) {
-        m_hgTabs->updateFileStates();
-        updateToolBarStyle();
-        hgRefresh();
-    }
-}
-
-void MainWindow::updateFileSystemWatcher()
-{
-    bool justCreated = false;
-    if (!m_fsWatcher) {
-        m_fsWatcher = new QFileSystemWatcher();
-        justCreated = true;
-    }
-
-    // QFileSystemWatcher will refuse to add a file or directory to
-    // its watch list that it is already watching -- fine, that's what
-    // we want -- but it prints a warning when this happens, which is
-    // annoying because it would be the normal case for us.  So we'll
-    // check for duplicates ourselves.
-    QSet<QString> alreadyWatched;
-    QStringList dl(m_fsWatcher->directories());
-    foreach (QString d, dl) alreadyWatched.insert(d);
-    
-    std::deque<QString> pending;
-    pending.push_back(m_workFolderPath);
-
-    while (!pending.empty()) {
-
-        QString path = pending.front();
-        pending.pop_front();
-        if (!alreadyWatched.contains(path)) {
-            m_fsWatcher->addPath(path);
-            DEBUG << "Added to file system watcher: " << path << endl;
-        }
-
-        QDir d(path);
-        if (d.exists()) {
-            d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot |
-                        QDir::Readable | QDir::NoSymLinks);
-            foreach (QString entry, d.entryList()) {
-                if (entry.startsWith('.')) continue;
-                QString entryPath = d.absoluteFilePath(entry);
-                pending.push_back(entryPath);
-            }
-        }
-    }
-
-    // The general timer isn't really related to the fs watcher
-    // object, it just does something similar -- every now and then we
-    // do a refresh just to update the history dates etc
-
-    m_fsWatcherGeneralTimer = new QTimer(this);
-    connect(m_fsWatcherGeneralTimer, SIGNAL(timeout()),
-            this, SLOT(checkFilesystem()));
-    m_fsWatcherGeneralTimer->setInterval(30 * 60 * 1000); // half an hour
-    m_fsWatcherGeneralTimer->start();
-
-    if (justCreated) {
-        connect(m_fsWatcher, SIGNAL(directoryChanged(QString)),
-                this, SLOT(fsDirectoryChanged(QString)));
-        connect(m_fsWatcher, SIGNAL(fileChanged(QString)),
-                this, SLOT(fsFileChanged(QString)));
-    }
-}
-
-void MainWindow::suspendFileSystemWatcher()
-{
-    DEBUG << "MainWindow::suspendFileSystemWatcher" << endl;
-    if (m_fsWatcher) {
-        m_fsWatcherSuspended = true;
-        if (m_fsWatcherRestoreTimer) {
-            delete m_fsWatcherRestoreTimer;
-            m_fsWatcherRestoreTimer = 0;
-        }
-        m_fsWatcherGeneralTimer->stop();
-    }
-}
-
-void MainWindow::restoreFileSystemWatcher()
-{
-    DEBUG << "MainWindow::restoreFileSystemWatcher" << endl;
-    if (m_fsWatcherRestoreTimer) delete m_fsWatcherRestoreTimer;
-        
-    // The restore timer is used to leave a polite interval between
-    // being asked to restore the watcher and actually doing so.  It's
-    // a single shot timer each time it's used, but we don't use
-    // QTimer::singleShot because we want to stop the previous one if
-    // it's running (via deleting it)
-
-    m_fsWatcherRestoreTimer = new QTimer(this);
-    connect(m_fsWatcherRestoreTimer, SIGNAL(timeout()),
-            this, SLOT(actuallyRestoreFileSystemWatcher()));
-    m_fsWatcherRestoreTimer->setInterval(1000);
-    m_fsWatcherRestoreTimer->setSingleShot(true);
-    m_fsWatcherRestoreTimer->start();
-}
-
-void MainWindow::actuallyRestoreFileSystemWatcher()
-{
-    DEBUG << "MainWindow::actuallyRestoreFileSystemWatcher" << endl;
-    if (m_fsWatcher) {
-        m_fsWatcherSuspended = false;
-        m_fsWatcherGeneralTimer->start();
-    }
-}
-
-void MainWindow::checkFilesystem()
-{
-    DEBUG << "MainWindow::checkFilesystem" << endl;
-    hgRefresh();
-}
-
-void MainWindow::fsDirectoryChanged(QString d)
-{
-    DEBUG << "MainWindow::fsDirectoryChanged " << d << endl;
-    if (!m_fsWatcherSuspended) {
-        hgStat();
-    }
-}
-
-void MainWindow::fsFileChanged(QString f)
-{
-    DEBUG << "MainWindow::fsFileChanged " << f << endl;
-    if (!m_fsWatcherSuspended) {
-        hgStat();
-    }
-}
-
-QString MainWindow::format1(QString head)
-{
-    return QString("<qt><h3>%1</h3></qt>").arg(head);
-}    
-
-QString MainWindow::format3(QString head, QString intro, QString code)
-{
-    code = xmlEncode(code).replace("\n", "<br>")
-#ifndef Q_OS_WIN32
-           // The hard hyphen comes out funny on Windows
-           .replace("-", "&#8209;")
-#endif
-           .replace(" ", "&nbsp;");
-    if (intro == "") {
-        return QString("<qt><h3>%1</h3><p><code>%2</code></p>")
-            .arg(head).arg(code);
-    } else if (code == "") {
-        return QString("<qt><h3>%1</h3><p>%2</p>")
-            .arg(head).arg(intro);
-    } else {
-        return QString("<qt><h3>%1</h3><p>%2</p><p><code>%3</code></p>")
-            .arg(head).arg(intro).arg(code);
-    }
-}
-
-void MainWindow::showIncoming(QString output)
-{
-    m_runner->hide();
-    IncomingDialog *d = new IncomingDialog(this, output);
-    d->exec();
-    delete d;
-}
-
-int MainWindow::extractChangeCount(QString text)
-{
-    QRegExp re("added (\\d+) ch\\w+ with (\\d+) ch\\w+ to (\\d+) f\\w+");
-    if (re.indexIn(text) >= 0) {
-        return re.cap(1).toInt();
-    } else if (text.contains("no changes")) {
-        return 0;
-    } else {
-        return -1; // unknown
-    }
-}
-
-void MainWindow::showPushResult(QString output)
-{
-    QString head;
-    QString report;
-    int n = extractChangeCount(output);
-    if (n > 0) {
-        head = tr("Pushed %n changeset(s)", "", n);
-        report = tr("<qt>Successfully pushed to the remote repository at <code>%1</code>.</qt>").arg(xmlEncode(m_remoteRepoPath));
-    } else if (n == 0) {
-        head = tr("No changes to push");
-        report = tr("The remote repository already contains all changes that have been committed locally.");
-        if (m_hgTabs->canCommit()) {
-            report = tr("%1<p>You do have some uncommitted changes. If you wish to push those to the remote repository, commit them locally first.").arg(report);
-        }            
-    } else {
-        head = tr("Push complete");
-    }
-    m_runner->hide();
-
-    MoreInformationDialog::information(this, tr("Push complete"),
-                                       head, report, output);
-}
-
-void MainWindow::showPullResult(QString output)
-{
-    QString head;
-    QString report;
-    int n = extractChangeCount(output);
-    if (n > 0) {
-        head = tr("Pulled %n changeset(s)", "", n);
-        report = tr("New changes will be highlighted in yellow in the history.");
-    } else if (n == 0) {
-        head = tr("No changes to pull");
-        report = tr("Your local repository already contains all changes found in the remote repository.");
-    } else {
-        head = tr("Pull complete");
-    }
-    m_runner->hide();
-
-    MoreInformationDialog::information(this, tr("Pull complete"),
-                                       head, report, output);
-}
-
-void MainWindow::reportNewRemoteHeads(QString output)
-{
-    bool headsAreLocal = false;
-
-    if (m_currentParents.size() == 1) {
-        int m_currentBranchHeads = 0;
-        bool parentIsHead = false;
-        Changeset *parent = m_currentParents[0];
-        foreach (Changeset *head, m_currentHeads) {
-            if (head->isOnBranch(m_currentBranch)) {
-                ++m_currentBranchHeads;
-            }
-            if (parent->id() == head->id()) {
-                parentIsHead = true;
-            }
-        }
-        if (m_currentBranchHeads == 2 && parentIsHead) {
-            headsAreLocal = true;
-        }
-    }
-
-    if (headsAreLocal) {
-        MoreInformationDialog::warning
-            (this,
-             tr("Push failed"),
-             tr("Push failed"),
-             tr("Your local repository could not be pushed to the remote repository.<br><br>You may need to merge the changes locally first."),
-             output);
-    } else if (m_hgTabs->canCommit() && m_currentParents.size() > 1) {
-        MoreInformationDialog::warning
-            (this,
-             tr("Push failed"),
-             tr("Push failed"),
-             tr("Your local repository could not be pushed to the remote repository.<br><br>You have an uncommitted merge in your local folder.  You probably need to commit it before you push."),
-             output);
-    } else {
-        MoreInformationDialog::warning
-            (this,
-             tr("Push failed"),
-             tr("Push failed"),
-             tr("Your local repository could not be pushed to the remote repository.<br><br>The remote repository may have been changed by someone else since you last pushed. Try pulling and merging their changes into your local repository first."),
-             output);
-    }
-}
-
-void MainWindow::commandStarting(HgAction action)
-{
-    // Annoyingly, hg stat actually modifies the working directory --
-    // it creates files called hg-checklink and hg-checkexec to test
-    // properties of the filesystem.  For safety's sake, suspend the
-    // fs watcher while running commands, and restore it shortly after
-    // a command has finished.
-
-    if (action.action == ACT_STAT) {
-        suspendFileSystemWatcher();
-    }
-}
-
-void MainWindow::commandFailed(HgAction action, QString output)
-{
-    DEBUG << "MainWindow::commandFailed" << endl;
-    restoreFileSystemWatcher();
-
-    QString setstr;
-#ifdef Q_OS_MAC
-    setstr = tr("Preferences");
-#else
-    setstr = tr("Settings");
-#endif
-
-    // Some commands we just have to ignore bad return values from:
-
-    switch(action.action) {
-    case ACT_NONE:
-        // uh huh
-        return;
-    case ACT_TEST_HG:
-        MoreInformationDialog::warning
-            (this,
-             tr("Failed to run Mercurial"),
-             tr("Failed to run Mercurial"),
-             tr("The Mercurial program either could not be found or failed to run.<br>Check that the Mercurial program path is correct in %1.").arg(setstr),
-             output);
-        settings();
-        return;
-    case ACT_TEST_HG_EXT:
-        MoreInformationDialog::warning
-            (this,
-             tr("Failed to run Mercurial"),
-             tr("Failed to run Mercurial with extension enabled"),
-             tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.<br>This may indicate an installation problem.<br><br>You may be able to continue working if you switch off &ldquo;Use EasyHg Mercurial Extension&rdquo; in %1.  Note that remote repositories that require authentication might not work if you do this.").arg(setstr),
-             output);
-        settings();
-        return;
-    case ACT_CLONEFROMREMOTE:
-        // if clone fails, we have no repo
-        m_workFolderPath = "";
-        enableDisableActions();
-        break; // go on to default report
-    case ACT_INCOMING:
-        // returns non-zero code and no output if the check was
-        // successful but there are no changes pending
-        if (output.replace(QRegExp("(^|\\n)warning: [^\\n]*\\n"), "").trimmed() == "") {
-            showIncoming("");
-            return;
-        }
-        break; // go on to default report
-    case ACT_QUERY_HEADS:
-        // fails if repo is empty; we don't care (if there's a genuine
-        // problem, something else will fail too).  Pretend it
-        // succeeded, so that any further actions that are contingent
-        // on the success of the heads query get carried out properly.
-        commandCompleted(action, "");
-        return;
-    case ACT_FOLDERDIFF:
-    case ACT_CHGSETDIFF:
-        // external program, unlikely to be anything useful in stderr
-        // and some return with failure codes when something as basic
-        // as the user closing the window via the wm happens
-        return;
-    case ACT_PUSH:
-        if (output.contains("creates new remote heads")) {
-            reportNewRemoteHeads(output);
-            return;
-        }
-        break; // go on to default report
-    case ACT_MERGE:
-    case ACT_RETRY_MERGE:
-        MoreInformationDialog::information
-            (this, tr("Merge"), tr("Merge failed"),
-             tr("Some files were not merged successfully.<p>You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file's right-button menu."),
-             output);
-        return;
-    case ACT_STAT:
-        break; // go on to default report
-    default:
-        break;
-    }
-
-    QString command = action.executable;
-    if (command == "") command = "hg";
-    foreach (QString arg, action.params) {
-        command += " " + arg;
-    }
-
-    MoreInformationDialog::warning
-        (this,
-         tr("Command failed"),
-         tr("Command failed"),
-         tr("A Mercurial command failed to run correctly.  This may indicate an installation problem or some other problem with EasyMercurial.<br><br>See &ldquo;More Details&rdquo; for the command output."),
-         output);
-}
-
-void MainWindow::commandCompleted(HgAction completedAction, QString output)
-{
-    restoreFileSystemWatcher();
-    HGACTIONS action = completedAction.action;
-
-    if (action == ACT_NONE) return;
-
-    bool headsChanged = false;
-    QStringList oldHeadIds;
-
-    switch (action) {
-
-    case ACT_TEST_HG:
-        break;
-
-    case ACT_TEST_HG_EXT:
-        break;
-
-    case ACT_QUERY_PATHS:
-    {
-        DEBUG << "stdout is " << output << endl;
-        LogParser lp(output, "=");
-        LogList ll = lp.parse();
-        DEBUG << ll.size() << " results" << endl;
-        if (!ll.empty()) {
-            m_remoteRepoPath = lp.parse()[0]["default"].trimmed();
-            DEBUG << "Set remote path to " << m_remoteRepoPath << endl;
-        } else {
-            m_remoteRepoPath = "";
-        }
-        MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
-        MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
-        updateWorkFolderAndRepoNames();
-        break;
-    }
-
-    case ACT_QUERY_BRANCH:
-        m_currentBranch = output.trimmed();
-        break;
-
-    case ACT_STAT:
-        m_lastStatOutput = output;
-        updateFileSystemWatcher();
-        break;
-
-    case ACT_RESOLVE_LIST:
-        if (output != "") {
-            // Remove lines beginning with R (they are resolved,
-            // and the file stat parser treats R as removed)
-            QStringList outList = output.split('\n');
-            QStringList winnowed;
-            foreach (QString line, outList) {
-                if (!line.startsWith("R ")) winnowed.push_back(line);
-            }
-            output = winnowed.join("\n");
-        }
-        DEBUG << "m_lastStatOutput = " << m_lastStatOutput << endl;
-        DEBUG << "resolve output = " << output << endl;
-        m_hgTabs->updateWorkFolderFileList(m_lastStatOutput + output);
-        break;
-
-    case ACT_RESOLVE_MARK:
-        m_shouldHgStat = true;
-        break;
-        
-    case ACT_INCOMING:
-        showIncoming(output);
-        break;
-
-    case ACT_ANNOTATE:
-    {
-        AnnotateDialog dialog(this, output);
-        dialog.exec();
-        m_shouldHgStat = true;
-        break;
-    }
-        
-    case ACT_PULL:
-        showPullResult(output);
-        m_shouldHgStat = true;
-        break;
-        
-    case ACT_PUSH:
-        showPushResult(output);
-        break;
-        
-    case ACT_INIT:
-        MultiChoiceDialog::addRecentArgument("init", m_workFolderPath);
-        MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
-        enableDisableActions();
-        m_shouldHgStat = true;
-        break;
-        
-    case ACT_CLONEFROMREMOTE:
-        MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
-        MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
-        MultiChoiceDialog::addRecentArgument("remote", m_workFolderPath, true);
-        MoreInformationDialog::information
-            (this,
-             tr("Clone"),
-             tr("Clone successful"),
-             tr("The remote repository was successfully cloned to the local folder <code>%1</code>.").arg(xmlEncode(m_workFolderPath)),
-             output);
-        enableDisableActions();
-        m_shouldHgStat = true;
-        break;
-        
-    case ACT_LOG:
-        m_hgTabs->setNewLog(output);
-        m_needNewLog = false;
-        break;
-        
-    case ACT_LOG_INCREMENTAL:
-        m_hgTabs->addIncrementalLog(output);
-        break;
-        
-    case ACT_QUERY_PARENTS:
-    {
-        foreach (Changeset *cs, m_currentParents) delete cs;
-        m_currentParents = Changeset::parseChangesets(output);
-        QStringList parentIds = Changeset::getIds(m_currentParents);
-        m_hgTabs->setCurrent(parentIds, m_currentBranch);
-    }
-        break;
-        
-    case ACT_QUERY_HEADS:
-    {
-        oldHeadIds = Changeset::getIds(m_currentHeads);
-        Changesets newHeads = Changeset::parseChangesets(output);
-        QStringList newHeadIds = Changeset::getIds(newHeads);
-        if (oldHeadIds != newHeadIds) {
-            DEBUG << "Heads changed, will prompt an incremental log if appropriate" << endl;
-            DEBUG << "Old heads: " << oldHeadIds.join(",") << endl;
-            DEBUG << "New heads: " << newHeadIds.join(",") << endl;
-            headsChanged = true;
-            foreach (Changeset *cs, m_currentHeads) delete cs;
-            m_currentHeads = newHeads;
-        }
-    }
-        break;
-
-    case ACT_COMMIT:
-        m_hgTabs->clearSelections();
-        m_justMerged = false;
-        m_shouldHgStat = true;
-        break;
-
-    case ACT_REVERT:
-        hgMarkFilesResolved(m_lastRevertedFiles);
-        m_justMerged = false;
-        break;
-        
-    case ACT_REMOVE:
-    case ACT_ADD:
-        m_hgTabs->clearSelections();
-        m_shouldHgStat = true;
-        break;
-
-    case ACT_TAG:
-        m_needNewLog = true;
-        m_shouldHgStat = true;
-        break;
-
-    case ACT_NEW_BRANCH:
-        m_shouldHgStat = true;
-        break;
-
-    case ACT_UNCOMMITTED_SUMMARY:
-        QMessageBox::information(this, tr("Change summary"),
-                                 format3(tr("Summary of uncommitted changes"),
-                                         "",
-                                         output));
-        break;
-
-    case ACT_DIFF_SUMMARY:
-    {
-        // Output has log info first, diff following after a blank line
-        output.replace("\r\n", "\n");
-        QStringList olist = output.split("\n\n", QString::SkipEmptyParts);
-        if (olist.size() > 1) output = olist[1];
-
-        Changeset *cs = (Changeset *)completedAction.extraData;
-        if (cs) {
-            QMessageBox::information
-                (this, tr("Change summary"),
-                 format3(tr("Summary of changes"),
-                         cs->formatHtml(),
-                         output));
-        } else if (output == "") {
-            // Can happen, for a merge commit (depending on parent)
-            QMessageBox::information(this, tr("Change summary"),
-                                     format3(tr("Summary of changes"),
-                                             tr("No changes"),
-                                             output));
-        } else {
-            QMessageBox::information(this, tr("Change summary"),
-                                     format3(tr("Summary of changes"),
-                                             "",
-                                             output));
-        }            
-        break;
-    }
-
-    case ACT_FOLDERDIFF:
-    case ACT_CHGSETDIFF:
-    case ACT_SERVE:
-    case ACT_HG_IGNORE:
-        m_shouldHgStat = true;
-        break;
-        
-    case ACT_UPDATE:
-        QMessageBox::information(this, tr("Update"), tr("<qt><h3>Update successful</h3><p>%1</p>").arg(xmlEncode(output)));
-        m_shouldHgStat = true;
-        break;
-        
-    case ACT_MERGE:
-        MoreInformationDialog::information
-            (this, tr("Merge"), tr("Merge successful"),
-             tr("Remember to test and commit the result before making any further changes."),
-             output);
-        m_shouldHgStat = true;
-        m_justMerged = true;
-        break;
-        
-    case ACT_RETRY_MERGE:
-        QMessageBox::information(this, tr("Resolved"),
-                                 tr("<qt><h3>Merge resolved</h3><p>Merge resolved successfully.<br>Remember to test and commit the result before making any further changes.</p>"));
-        m_shouldHgStat = true;
-        m_justMerged = true;
-        break;
-        
-    default:
-        break;
-    }
-
-    // Sequence when no full log required:
-    //   paths -> branch -> stat -> resolve-list -> heads ->
-    //     incremental-log (only if heads changed) -> parents
-    // 
-    // Sequence when full log required:
-    //   paths -> branch -> stat -> resolve-list -> heads -> parents -> log
-    //
-    // Note we want to call enableDisableActions only once, at the end
-    // of whichever sequence is in use.
-
-    bool noMore = false;
-
-    switch (action) {
-
-    case ACT_TEST_HG:
-    {
-        QSettings settings;
-        settings.beginGroup("General");
-        if (settings.value("useextension", true).toBool()) {
-            hgTestExtension();
-        } else if (m_workFolderPath == "") {
-            open();
-        } else {
-            hgQueryPaths();
-        }
-        break;
-    }
-        
-    case ACT_TEST_HG_EXT:
-        if (m_workFolderPath == "") {
-            open();
-        } else{
-            hgQueryPaths();
-        }
-        break;
-        
-    case ACT_QUERY_PATHS:
-        hgQueryBranch();
-        break;
-
-    case ACT_QUERY_BRANCH:
-        hgStat();
-        break;
-        
-    case ACT_STAT:
-        hgResolveList();
-        break;
-
-    case ACT_RESOLVE_LIST:
-        hgQueryHeads();
-        break;
-
-    case ACT_QUERY_HEADS:
-        if (headsChanged && !m_needNewLog) {
-            hgLogIncremental(oldHeadIds);
-        } else {
-            hgQueryParents();
-        }
-        break;
-
-    case ACT_LOG_INCREMENTAL:
-        hgQueryParents();
-        break;
-
-    case ACT_QUERY_PARENTS:
-        if (m_needNewLog) {
-            hgLog();
-        } else {
-            // we're done
-            noMore = true;
-        }
-        break;
-
-    case ACT_LOG:
-        // we're done
-        noMore = true;
-        break;
-
-    default:
-        if (m_shouldHgStat) {
-            m_shouldHgStat = false;
-            hgQueryPaths();
-        } else {
-            noMore = true;
-        }
-        break;
-    }
-
-    if (noMore) {
-        m_stateUnknown = false;
-        enableDisableActions();
-        m_hgTabs->updateHistory();
-    }
-}
-
-void MainWindow::connectActions()
-{
-    connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close()));
-    connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about()));
-
-    connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh()));
-    connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove()));
-    connect(m_hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd()));
-    connect(m_hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit()));
-    connect(m_hgFolderDiffAct, SIGNAL(triggered()), this, SLOT(hgFolderDiff()));
-    connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate()));
-    connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert()));
-    connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge()));
-    connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore()));
-
-    connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings()));
-    connect(m_openAct, SIGNAL(triggered()), this, SLOT(open()));
-    connect(m_changeRemoteRepoAct, SIGNAL(triggered()), this, SLOT(changeRemoteRepo()));
-
-    connect(m_hgIncomingAct, SIGNAL(triggered()), this, SLOT(hgIncoming()));
-    connect(m_hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull()));
-    connect(m_hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush()));
-
-    connect(m_hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe()));
-}
-
-void MainWindow::connectTabsSignals()
-{
-    connect(m_hgTabs, SIGNAL(currentChanged(int)),
-            this, SLOT(enableDisableActions()));
-
-    connect(m_hgTabs, SIGNAL(commit()),
-            this, SLOT(hgCommit()));
-    
-    connect(m_hgTabs, SIGNAL(revert()),
-            this, SLOT(hgRevert()));
-    
-    connect(m_hgTabs, SIGNAL(diffWorkingFolder()),
-            this, SLOT(hgFolderDiff()));
-    
-    connect(m_hgTabs, SIGNAL(showSummary()),
-            this, SLOT(hgShowSummary()));
-    
-    connect(m_hgTabs, SIGNAL(newBranch()),
-            this, SLOT(hgNewBranch()));
-    
-    connect(m_hgTabs, SIGNAL(noBranch()),
-            this, SLOT(hgNoBranch()));
-
-    connect(m_hgTabs, SIGNAL(updateTo(QString)),
-            this, SLOT(hgUpdateToRev(QString)));
-
-    connect(m_hgTabs, SIGNAL(diffToCurrent(QString)),
-            this, SLOT(hgDiffToCurrent(QString)));
-
-    connect(m_hgTabs, SIGNAL(diffToParent(QString, QString)),
-            this, SLOT(hgDiffToParent(QString, QString)));
-
-    connect(m_hgTabs, SIGNAL(showSummary(Changeset *)),
-            this, SLOT(hgShowSummaryFor(Changeset *)));
-
-    connect(m_hgTabs, SIGNAL(mergeFrom(QString)),
-            this, SLOT(hgMergeFrom(QString)));
-
-    connect(m_hgTabs, SIGNAL(newBranch(QString)),
-            this, SLOT(hgNewBranch()));
-
-    connect(m_hgTabs, SIGNAL(tag(QString)),
-            this, SLOT(hgTag(QString)));
-
-    connect(m_hgTabs, SIGNAL(annotateFiles(QStringList)),
-            this, SLOT(hgAnnotateFiles(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(diffFiles(QStringList)),
-            this, SLOT(hgDiffFiles(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(commitFiles(QStringList)),
-            this, SLOT(hgCommitFiles(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(revertFiles(QStringList)),
-            this, SLOT(hgRevertFiles(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(addFiles(QStringList)),
-            this, SLOT(hgAddFiles(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(removeFiles(QStringList)),
-            this, SLOT(hgRemoveFiles(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(redoFileMerges(QStringList)),
-            this, SLOT(hgRedoFileMerges(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(markFilesResolved(QStringList)),
-            this, SLOT(hgMarkFilesResolved(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(ignoreFiles(QStringList)),
-            this, SLOT(hgIgnoreFiles(QStringList)));
-
-    connect(m_hgTabs, SIGNAL(unIgnoreFiles(QStringList)),
-            this, SLOT(hgUnIgnoreFiles(QStringList)));
-}    
-
-void MainWindow::enableDisableActions()
-{
-    DEBUG << "MainWindow::enableDisableActions" << endl;
-
-    QString dirname = QDir(m_workFolderPath).dirName();
-
-    if (m_workFolderPath != "") { // dirname of "" is ".", so test path instead
-        setWindowTitle(tr("EasyMercurial: %1").arg(dirname));
-    } else {
-        setWindowTitle(tr("EasyMercurial"));
-    }
-
-    //!!! should also do things like set the status texts for the
-    //!!! actions appropriately by context
-
-    QDir localRepoDir;
-    QDir workFolderDir;
-    bool workFolderExist = true;
-    bool localRepoExist = true;
-
-    m_remoteRepoActionsEnabled = true;
-    if (m_remoteRepoPath.isEmpty()) {
-        m_remoteRepoActionsEnabled = false;
-    }
-
-    m_localRepoActionsEnabled = true;
-    if (m_workFolderPath.isEmpty()) {
-        m_localRepoActionsEnabled = false;
-        workFolderExist = false;
-    }
-
-    if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) {
-        m_localRepoActionsEnabled = false;
-        workFolderExist = false;
-    } else {
-        workFolderExist = true;
-    }
-
-    if (!localRepoDir.exists(m_workFolderPath + "/.hg")) {
-        m_localRepoActionsEnabled = false;
-        localRepoExist = false;
-    }
-
-    m_hgIncomingAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled);
-    m_hgPullAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled);
-    m_hgPushAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled);
-
-    bool haveDiff = false;
-    QSettings settings;
-    settings.beginGroup("Locations");
-    if (settings.value("extdiffbinary", "").toString() != "") {
-        haveDiff = true;
-    }
-    settings.endGroup();
-
-    m_hgRefreshAct -> setEnabled(m_localRepoActionsEnabled);
-    m_hgFolderDiffAct -> setEnabled(m_localRepoActionsEnabled && haveDiff);
-    m_hgRevertAct -> setEnabled(m_localRepoActionsEnabled);
-    m_hgAddAct -> setEnabled(m_localRepoActionsEnabled);
-    m_hgRemoveAct -> setEnabled(m_localRepoActionsEnabled);
-    m_hgUpdateAct -> setEnabled(m_localRepoActionsEnabled);
-    m_hgCommitAct -> setEnabled(m_localRepoActionsEnabled);
-    m_hgMergeAct -> setEnabled(m_localRepoActionsEnabled);
-    m_hgServeAct -> setEnabled(m_localRepoActionsEnabled);
-    m_hgIgnoreAct -> setEnabled(m_localRepoActionsEnabled);
-
-    DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl;
-    DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl;
-
-    m_hgAddAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canAdd());
-    m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRemove());
-    m_hgCommitAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canCommit());
-    m_hgRevertAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRevert());
-    m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canDiff());
-
-    // A default merge makes sense if:
-    //  * there is only one parent (if there are two, we have an uncommitted merge) and
-    //  * there are exactly two heads that have the same branch as the current branch and
-    //  * our parent is one of those heads
-    //
-    // A default update makes sense if:
-    //  * there is only one parent and
-    //  * the parent is not one of the current heads
-
-    bool canMerge = false;
-    bool canUpdate = false;
-    bool haveMerge = false;
-    bool emptyRepo = false;
-    bool noWorkingCopy = false;
-    bool newBranch = false;
-    int m_currentBranchHeads = 0;
-
-    if (m_currentParents.size() == 1) {
-        bool parentIsHead = false;
-        Changeset *parent = m_currentParents[0];
-        foreach (Changeset *head, m_currentHeads) {
-            DEBUG << "head branch " << head->branch() << ", current branch " << m_currentBranch << endl;
-            if (head->isOnBranch(m_currentBranch)) {
-                ++m_currentBranchHeads;
-            }
-            if (parent->id() == head->id()) {
-                parentIsHead = true;
-            }
-        }
-        if (m_currentBranchHeads == 2 && parentIsHead) {
-            canMerge = true;
-        }
-        if (m_currentBranchHeads == 0 && parentIsHead) {
-            // Just created a new branch
-            newBranch = true;
-        }
-        if (!parentIsHead) {
-            canUpdate = true;
-            DEBUG << "parent id = " << parent->id() << endl;
-            DEBUG << " head ids "<<endl;
-            foreach (Changeset *h, m_currentHeads) {
-                DEBUG << "head id = " << h->id() << endl;
-            }
-        }
-        m_justMerged = false;
-    } else if (m_currentParents.size() == 0) {
-        if (m_currentHeads.size() == 0) {
-            // No heads -> empty repo
-            emptyRepo = true;
-        } else {
-            // Heads, but no parents -> no working copy, e.g. we have
-            // just converted this repo but haven't updated in it yet.
-            // Uncommon but confusing; probably merits a special case
-            noWorkingCopy = true;
-            canUpdate = true;
-        }
-        m_justMerged = false;
-    } else {
-        haveMerge = true;
-        m_justMerged = true;
-    }
-        
-    m_hgMergeAct->setEnabled(m_localRepoActionsEnabled &&
-                           (canMerge || m_hgTabs->canResolve()));
-    m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled &&
-                            (canUpdate && !m_hgTabs->haveChangesToCommit()));
-
-    // Set the state field on the file status widget
-
-    QString branchText;
-    if (m_currentBranch == "" || m_currentBranch == "default") {
-        branchText = tr("the default branch");
-    } else {
-        branchText = tr("branch \"%1\"").arg(m_currentBranch);
-    }
-
-    if (m_stateUnknown) {
-        if (m_workFolderPath == "") {
-            m_workStatus->setState(tr("No repository open"));
-        } else {
-            m_workStatus->setState(tr("(Examining repository)"));
-        }
-    } else if (emptyRepo) {
-        m_workStatus->setState(tr("Nothing committed to this repository yet"));
-    } else if (noWorkingCopy) {
-        m_workStatus->setState(tr("No working copy yet: consider updating"));
-    } else if (canMerge) {
-        m_workStatus->setState(tr("<b>Awaiting merge</b> on %1").arg(branchText));
-    } else if (!m_hgTabs->getAllUnresolvedFiles().empty()) {
-        m_workStatus->setState(tr("Have unresolved files following merge on %1").arg(branchText));
-    } else if (haveMerge) {
-        m_workStatus->setState(tr("Have merged but not yet committed on %1").arg(branchText));
-    } else if (newBranch) {
-        m_workStatus->setState(tr("On %1.  New branch: has not yet been committed").arg(branchText));
-    } else if (canUpdate) {
-        if (m_hgTabs->haveChangesToCommit()) {
-            // have uncommitted changes
-            m_workStatus->setState(tr("On %1. Not at the head of the branch").arg(branchText));
-        } else {
-            // no uncommitted changes
-            m_workStatus->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText));
-        }
-    } else if (m_currentBranchHeads > 1) {
-        m_workStatus->setState(tr("At one of %n heads of %1", "", m_currentBranchHeads).arg(branchText));
-    } else {
-        m_workStatus->setState(tr("At the head of %1").arg(branchText));
-    }
-}
-
-void MainWindow::createActions()
-{
-    //File actions
-    m_openAct = new QAction(QIcon(":/images/fileopen.png"), tr("Open..."), this);
-    m_openAct -> setStatusTip(tr("Open an existing repository or working folder"));
-
-    m_changeRemoteRepoAct = new QAction(tr("Change Remote Location..."), this);
-    m_changeRemoteRepoAct->setStatusTip(tr("Change the default remote repository for pull and push actions"));
-
-    m_settingsAct = new QAction(QIcon(":/images/settings.png"), tr("Settings..."), this);
-    m_settingsAct -> setStatusTip(tr("View and change application settings"));
-
-    m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("Quit"), this);
-    m_exitAct->setShortcuts(QKeySequence::Quit);
-    m_exitAct->setStatusTip(tr("Quit EasyMercurial"));
-
-    //Repository actions
-    m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("Refresh"), this);
-    m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder"));
-
-    m_hgIncomingAct = new QAction(QIcon(":/images/incoming.png"), tr("Preview"), this);
-    m_hgIncomingAct -> setStatusTip(tr("See what changes are available in the remote repository waiting to be pulled"));
-
-    m_hgPullAct = new QAction(QIcon(":/images/pull.png"), tr("Pull"), this);
-    m_hgPullAct -> setStatusTip(tr("Pull changes from the remote repository to the local repository"));
-
-    m_hgPushAct = new QAction(QIcon(":/images/push.png"), tr("Push"), this);
-    m_hgPushAct->setStatusTip(tr("Push changes from the local repository to the remote repository"));
-
-    //Workfolder actions
-    m_hgFolderDiffAct   = new QAction(QIcon(":/images/folderdiff.png"), tr("Diff"), this);
-    m_hgFolderDiffAct->setStatusTip(tr("See what has changed in the working folder compared with the last committed state"));
-
-    m_hgRevertAct = new QAction(QIcon(":/images/undo.png"), tr("Revert"), this);
-    m_hgRevertAct->setStatusTip(tr("Throw away your changes and return to the last committed state"));
-
-    m_hgAddAct = new QAction(QIcon(":/images/add.png"), tr("Add"), this);
-    m_hgAddAct -> setStatusTip(tr("Mark the selected file(s) to be added on the next commit"));
-
-    //!!! needs to be modified for number
-    m_hgRemoveAct = new QAction(QIcon(":/images/remove.png"), tr("Remove"), this);
-    m_hgRemoveAct -> setStatusTip(tr("Mark the selected file(s) to be removed from version control on the next commit"));
-
-    m_hgUpdateAct = new QAction(QIcon(":/images/update.png"), tr("Update"), this);
-    m_hgUpdateAct->setStatusTip(tr("Update the working folder to the head of the current repository branch"));
-
-    //!!! needs to be modified when files selected
-    m_hgCommitAct = new QAction(QIcon(":/images/commit.png"), tr("Commit"), this);
-    m_hgCommitAct->setStatusTip(tr("Commit your changes to the local repository"));
-
-    m_hgMergeAct = new QAction(QIcon(":/images/merge.png"), tr("Merge"), this);
-    m_hgMergeAct->setStatusTip(tr("Merge the two independent sets of changes in the local repository into the working folder"));
-
-    //Advanced actions
-
-    m_hgIgnoreAct = new QAction(tr("Edit .hgignore File"), this);
-    m_hgIgnoreAct -> setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial"));
-
-    m_hgServeAct = new QAction(tr("Serve via HTTP"), this);
-    m_hgServeAct -> setStatusTip(tr("Serve local repository via http for workgroup access"));
-
-    //Help actions
-    m_aboutAct = new QAction(tr("About EasyMercurial"), this);
-
-    // Miscellaneous
-    QShortcut *clearSelectionsShortcut = new QShortcut(Qt::Key_Escape, this);
-    connect(clearSelectionsShortcut, SIGNAL(activated()),
-            this, SLOT(clearSelections()));
-}
-
-void MainWindow::createMenus()
-{
-    m_fileMenu = menuBar()->addMenu(tr("File"));
-
-    m_fileMenu -> addAction(m_openAct);
-    m_fileMenu -> addAction(m_changeRemoteRepoAct);
-    m_fileMenu -> addSeparator();
-
-    m_advancedMenu = m_fileMenu->addMenu(tr("Advanced"));
-
-    m_fileMenu -> addAction(m_settingsAct);
-
-    m_fileMenu -> addSeparator();
-    m_fileMenu -> addAction(m_exitAct);
-
-    m_advancedMenu -> addAction(m_hgIgnoreAct);
-    m_advancedMenu -> addSeparator();
-    m_advancedMenu -> addAction(m_hgServeAct);
-
-    m_helpMenu = menuBar()->addMenu(tr("Help"));
-    m_helpMenu->addAction(m_aboutAct);
-}
-
-void MainWindow::createToolBars()
-{
-    m_fileToolBar = addToolBar(tr("File"));
-    m_fileToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE));
-    m_fileToolBar -> addAction(m_openAct);
-    m_fileToolBar -> addAction(m_hgRefreshAct);
-    m_fileToolBar -> setMovable(false);
-
-    m_repoToolBar = addToolBar(tr(REPOMENU_TITLE));
-    m_repoToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE));
-    m_repoToolBar->addAction(m_hgIncomingAct);
-    m_repoToolBar->addAction(m_hgPullAct);
-    m_repoToolBar->addAction(m_hgPushAct);
-    m_repoToolBar -> setMovable(false);
-
-    m_workFolderToolBar = addToolBar(tr(WORKFOLDERMENU_TITLE));
-    addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar);
-    m_workFolderToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE));
-    m_workFolderToolBar->addAction(m_hgFolderDiffAct);
-    m_workFolderToolBar->addSeparator();
-    m_workFolderToolBar->addAction(m_hgRevertAct);
-    m_workFolderToolBar->addAction(m_hgUpdateAct);
-    m_workFolderToolBar->addAction(m_hgCommitAct);
-    m_workFolderToolBar->addAction(m_hgMergeAct);
-    m_workFolderToolBar->addSeparator();
-    m_workFolderToolBar->addAction(m_hgAddAct);
-    m_workFolderToolBar->addAction(m_hgRemoveAct);
-    m_workFolderToolBar -> setMovable(false);
-
-    updateToolBarStyle();
-}
-
-void MainWindow::updateToolBarStyle()
-{
-    QSettings settings;
-    settings.beginGroup("Presentation");
-    bool showText = settings.value("showiconlabels", true).toBool();
-    settings.endGroup();
-    
-    foreach (QToolButton *tb, findChildren<QToolButton *>()) {
-        tb->setToolButtonStyle(showText ?
-                               Qt::ToolButtonTextUnderIcon :
-                               Qt::ToolButtonIconOnly);
-    }
-}    
-
-void MainWindow::updateWorkFolderAndRepoNames()
-{
-    m_hgTabs->setLocalPath(m_workFolderPath);
-
-    m_workStatus->setLocalPath(m_workFolderPath);
-    m_workStatus->setRemoteURL(m_remoteRepoPath);
-}
-
-void MainWindow::createStatusBar()
-{
-    statusBar()->showMessage(tr("Ready"));
-}
-
-void MainWindow::readSettings()
-{
-    QDir workFolder;
-
-    QSettings settings;
-
-    m_remoteRepoPath = settings.value("remoterepopath", "").toString();
-    m_workFolderPath = settings.value("workfolderpath", "").toString();
-    if (!workFolder.exists(m_workFolderPath))
-    {
-        m_workFolderPath = "";
-    }
-
-    QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
-    QSize size = settings.value("size", QSize(550, 550)).toSize();
-    m_firstStart = settings.value("firststart", QVariant(true)).toBool();
-
-    resize(size);
-    move(pos);
-}
-
-void MainWindow::writeSettings()
-{
-    QSettings settings;
-    settings.setValue("pos", pos());
-    settings.setValue("size", size());
-    settings.setValue("remoterepopath", m_remoteRepoPath);
-    settings.setValue("workfolderpath", m_workFolderPath);
-    settings.setValue("firststart", m_firstStart);
-}
-
-
-
-
--- a/mainwindow.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,248 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 MAINWINDOW_H
-#define MAINWINDOW_H
-
-#include "hgtabwidget.h"
-#include "hgrunner.h"
-#include "common.h"
-#include "changeset.h"
-#include "hgaction.h"
-
-#include <QMainWindow>
-#include <QListWidget>
-#include <QFileSystemWatcher>
-
-QT_BEGIN_NAMESPACE
-class QAction;
-class QMenu;
-class QTimer;
-QT_END_NAMESPACE
-
-class WorkStatusWidget;
-
-class MainWindow : public QMainWindow
-{
-    Q_OBJECT
-
-public:
-    MainWindow(QString myDirPath);
-
-protected:
-    void closeEvent(QCloseEvent *event);
-
-public slots:
-    void open(QString local);
-    void hgRefresh();
-    void commandStarting(HgAction);
-    void commandCompleted(HgAction action, QString stdOut);
-    void commandFailed(HgAction action, QString stdErr);
-    void enableDisableActions();
-
-private slots:
-    void about();
-    void settings();
-    void open();
-    void changeRemoteRepo();
-    void startupDialog();
-    void clearSelections();
-    void showAllChanged(bool);
-
-    void hgTest();
-    void hgTestExtension();
-    void hgQueryPaths();
-    void hgStat();
-    void hgRemove();
-    void hgAdd();
-    void hgCommit();
-    void hgShowSummary();
-    void hgShowSummaryFor(Changeset *);
-    void hgFolderDiff();
-    void hgDiffToCurrent(QString);
-    void hgDiffToParent(QString, QString);
-    void hgUpdate();
-    void hgRevert();
-    void hgMerge();
-    void hgRedoMerge();
-    void hgCloneFromRemote();
-    void hgInit();
-    void hgIncoming();
-    void hgPush();
-    void hgPull();
-    void hgUpdateToRev(QString);
-    void hgMergeFrom(QString);
-    void hgResolveList();
-    void hgTag(QString);
-    void hgNewBranch();
-    void hgNoBranch();
-    void hgServe();
-    void hgIgnore();
-
-    void hgAnnotateFiles(QStringList);
-    void hgDiffFiles(QStringList);
-    void hgCommitFiles(QStringList);
-    void hgRevertFiles(QStringList);
-    void hgAddFiles(QStringList);
-    void hgRemoveFiles(QStringList);
-    void hgRedoFileMerges(QStringList);
-    void hgMarkFilesResolved(QStringList);
-    void hgIgnoreFiles(QStringList);
-    void hgUnIgnoreFiles(QStringList);
-
-    void fsDirectoryChanged(QString);
-    void fsFileChanged(QString);
-    void checkFilesystem();
-    void actuallyRestoreFileSystemWatcher();
-
-private:
-    void hgQueryBranch();
-    void hgQueryHeads();
-    void hgQueryParents();
-    void hgLog();
-    void hgLogIncremental(QStringList prune);
-    void createActions();
-    void connectActions();
-    void connectTabsSignals();
-    void createMenus();
-    void createToolBars();
-    void updateToolBarStyle();
-    void createStatusBar();
-    void readSettings();
-    void splitChangeSets(QStringList *list, QString hgLogOutput);
-    void reportNewRemoteHeads(QString);
-    void writeSettings();
-
-    QStringList listAllUpIpV4Addresses();
-    QString filterTag(QString tag);
-
-    QString getUserInfo() const;
-
-    bool openLocal(QString);
-    bool openRemote(QString, QString);
-    bool openInit(QString);
-
-    bool complainAboutFilePath(QString);
-    bool complainAboutUnknownFolder(QString);
-    bool complainAboutInitInRepo(QString);
-    bool complainAboutInitFile(QString);
-    bool complainAboutCloneToExisting(QString);
-    bool complainAboutCloneToFile(QString);
-    QString complainAboutCloneToExistingFolder(QString local, QString remote); // returns new location, or empty string for cancel
-
-    bool askAboutUnknownFolder(QString);
-    bool askToInitExisting(QString);
-    bool askToInitNew(QString);
-    bool askToOpenParentRepo(QString, QString);
-    bool askToOpenInsteadOfInit(QString);
-
-    void showIncoming(QString);
-    void showPullResult(QString);
-    void showPushResult(QString);
-    int extractChangeCount(QString);
-    QString format1(QString);
-    QString format3(QString, QString, QString);
-
-    void clearState();
-
-    void updateFileSystemWatcher();
-    void suspendFileSystemWatcher();
-    void restoreFileSystemWatcher();
-
-    void updateWorkFolderAndRepoNames();
-
-    WorkStatusWidget *m_workStatus;
-    HgTabWidget *m_hgTabs;
-
-    QString m_remoteRepoPath;
-    QString m_workFolderPath;
-    QString m_currentBranch;
-    Changesets m_currentHeads;
-    Changesets m_currentParents;
-    int m_commitsSincePush;
-    bool m_stateUnknown;
-    bool m_hgIsOK;
-    bool m_needNewLog;
-
-    bool m_firstStart;
-
-    bool m_showAllFiles;
-
-    //Actions enabled flags
-    bool m_remoteRepoActionsEnabled;
-    bool m_localRepoActionsEnabled;
-
-    QString m_myDirPath;
-
-    // File menu actions
-    QAction *m_openAct;
-    QAction *m_changeRemoteRepoAct;
-    QAction *m_settingsAct;
-    QAction *m_exitAct;
-
-    // Repo actions
-    QAction *m_hgIncomingAct;
-    QAction *m_hgPushAct;
-    QAction *m_hgPullAct;
-    QAction *m_hgRefreshAct;
-    QAction *m_hgFolderDiffAct;
-    QAction *m_hgChgSetDiffAct;
-    QAction *m_hgRevertAct;
-    QAction *m_hgAddAct;
-    QAction *m_hgRemoveAct;
-    QAction *m_hgUpdateAct;
-    QAction *m_hgCommitAct;
-    QAction *m_hgMergeAct;
-    QAction *m_hgUpdateToRevAct;
-    QAction *m_hgAnnotateAct;
-    QAction *m_hgIgnoreAct;
-    QAction *m_hgServeAct;
-
-    // Menus
-    QMenu *m_fileMenu;
-    QMenu *m_advancedMenu;
-    QMenu *m_helpMenu;
-
-    // Help menu actions
-    QAction *m_aboutAct;
-
-    QToolBar *m_fileToolBar;
-    QToolBar *m_repoToolBar;
-    QToolBar *m_workFolderToolBar;
-
-    HgRunner *m_runner;
-
-    bool m_shouldHgStat;
-
-    QString getDiffBinaryName();
-    QString getMergeBinaryName();
-    QString getEditorBinaryName();
-
-    QFileSystemWatcher *m_fsWatcher;
-    QTimer *m_fsWatcherGeneralTimer;
-    QTimer *m_fsWatcherRestoreTimer;
-    bool m_fsWatcherSuspended;
-
-    QString m_lastStatOutput;
-    QStringList m_lastRevertedFiles;
-
-    bool m_justMerged;
-    QString m_mergeTargetRevision;
-    QString m_mergeCommitComment;
-};
-
-#endif
--- a/moreinformationdialog.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 "moreinformationdialog.h"
-
-#include <QMessageBox>
-#include <QLabel>
-#include <QGridLayout>
-#include <QTextEdit>
-#include <QDialogButtonBox>
-#include <QPushButton>
-#include <QApplication>
-#include <QStyle>
-
-MoreInformationDialog::MoreInformationDialog(QString title,
-                                             QString head,
-                                             QString text,
-                                             QString more,
-                                             QWidget *parent) :
-    QDialog(parent)
-{
-    setWindowTitle(title);
-
-    QGridLayout *layout = new QGridLayout;
-    layout->setSpacing(10);
-    setLayout(layout);
-
-    m_iconLabel = new QLabel;
-    layout->addWidget(m_iconLabel, 0, 0, 2, 1, Qt::AlignTop);
-
-    QLabel *headLabel = new QLabel(QString("<qt><h3>%1</h3></qt>").arg(head));
-    layout->addWidget(headLabel, 0, 1);
-
-    QLabel *textLabel = new QLabel(text);
-    textLabel->setTextFormat(Qt::RichText);
-    textLabel->setWordWrap(true);
-    layout->addWidget(textLabel, 1, 1, Qt::AlignTop);
-
-    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
-    connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
-    layout->addWidget(bb, 2, 0, 1, 2);
-
-    m_moreButton = bb->addButton(tr("More Details..."),
-                                 QDialogButtonBox::ActionRole);
-    m_moreButton->setAutoDefault(false);
-    m_moreButton->setDefault(false);
-
-    connect(m_moreButton, SIGNAL(clicked()), this, SLOT(moreClicked()));
-
-    bb->button(QDialogButtonBox::Ok)->setDefault(true);
-
-    m_moreText = new QTextEdit();
-    m_moreText->setAcceptRichText(false);
-    m_moreText->document()->setPlainText(more);
-    m_moreText->setMinimumWidth(360);
-    m_moreText->setReadOnly(true);
-    m_moreText->setLineWrapMode(QTextEdit::NoWrap);
-
-    QFont font("Monospace");
-    font.setStyleHint(QFont::TypeWriter);
-    m_moreText->setFont(font);
-
-    layout->addWidget(m_moreText, 3, 0, 1, 2);
-
-    m_moreText->hide();
-    if (more == "") m_moreButton->hide();
-
-    layout->setRowStretch(1, 20);
-    layout->setColumnStretch(1, 20);
-    setMinimumWidth(400);
-}
-
-MoreInformationDialog::~MoreInformationDialog()
-{
-}
-
-void
-MoreInformationDialog::moreClicked()
-{
-    if (m_moreText->isVisible()) {
-        m_moreText->hide();
-        m_moreButton->setText(tr("Show Details..."));
-    } else {
-        m_moreText->show();
-        m_moreButton->setText(tr("Hide Details..."));
-    }
-    adjustSize();
-}        
-
-void
-MoreInformationDialog::setIcon(QIcon icon)
-{
-    QStyle *style = qApp->style();
-    int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this);
-    m_iconLabel->setPixmap(icon.pixmap(iconSize, iconSize));
-}
-
-void
-MoreInformationDialog::critical(QWidget *parent, QString title, QString head,
-				QString text, QString more)
-{
-    MoreInformationDialog d(title, head, text, more, parent);
-    QStyle *style = qApp->style();
-    d.setIcon(style->standardIcon(QStyle::SP_MessageBoxCritical, 0, &d));
-    d.exec();
-}
-
-void
-MoreInformationDialog::information(QWidget *parent, QString title, QString head,
-                                   QString text, QString more)
-{
-    MoreInformationDialog d(title, head, text, more, parent);
-    QStyle *style = qApp->style();
-    d.setIcon(style->standardIcon(QStyle::SP_MessageBoxInformation, 0, &d));
-    d.exec();
-}
-
-void
-MoreInformationDialog::warning(QWidget *parent, QString title, QString head,
-                               QString text, QString more)
-{
-    MoreInformationDialog d(title, head, text, more, parent);
-    QStyle *style = qApp->style();
-    d.setIcon(style->standardIcon(QStyle::SP_MessageBoxWarning, 0, &d));
-    d.exec();
-}
-
--- a/moreinformationdialog.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on hgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 MORE_INFORMATION_DIALOG_H
-#define MORE_INFORMATION_DIALOG_H
-
-#include <QString>
-#include <QDialog>
-
-class QLabel;
-class QTextEdit;
-class QPushButton;
-
-/**
- * Provide methods like the QMessageBox static methods, to call up
- * dialogs with "More information" buttons in them.  QMessageBox does
- * have an optional additional-details field, but it doesn't behave
- * quite as we'd like with regard to layout
- */
-
-class MoreInformationDialog : public QDialog
-{
-    Q_OBJECT
-
-public:
-    MoreInformationDialog(QString title,
-                          QString head,
-                          QString text,
-                          QString more,
-                          QWidget *parent = 0);
-
-    ~MoreInformationDialog();
-
-    void setIcon(QIcon);
-
-    static void critical(QWidget *parent, QString title, QString head, QString text, QString more);
-    static void information(QWidget *parent, QString title, QString head, QString text, QString more);
-    static void warning(QWidget *parent, QString title, QString head, QString text, QString more);
-
-private slots:
-    void moreClicked();
-
-private:
-    QLabel *m_iconLabel;
-    QPushButton *m_moreButton;
-    QTextEdit *m_moreText;
-};
-
-#endif
--- a/multichoicedialog.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,350 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 "multichoicedialog.h"
-
-#include "selectablelabel.h"
-
-#include "debug.h"
-
-#include <QDialogButtonBox>
-#include <QToolButton>
-#include <QPushButton>
-#include <QFont>
-#include <QDir>
-#include <QFileDialog>
-#include <QDesktopServices>
-#include <QUrl>
-
-MultiChoiceDialog::MultiChoiceDialog(QString title, QString heading,
-                                     QString helpUrl, QWidget *parent) :
-    QDialog(parent),
-    m_helpUrl(helpUrl)
-{
-    setModal(true);
-    setWindowTitle(title);
-
-    QGridLayout *outer = new QGridLayout;
-    setLayout(outer);
-
-    outer->addWidget(new QLabel(heading), 0, 0, 1, 3);
-
-    QWidget *innerWidget = new QWidget;
-    outer->addWidget(innerWidget, 1, 0, 1, 3);
-    m_choiceLayout = new QHBoxLayout;
-    innerWidget->setLayout(m_choiceLayout);
-
-    m_descriptionLabel = new QLabel;
-    outer->addWidget(m_descriptionLabel, 2, 0, 1, 3);
-
-    QFont f = m_descriptionLabel->font();
-    f.setPointSize(f.pointSize() * 0.95);
-    m_descriptionLabel->setFont(f);
-
-    m_urlLabel = new QLabel(tr("&URL:"));
-    outer->addWidget(m_urlLabel, 3, 0);
-
-    m_urlCombo = new QComboBox();
-    m_urlCombo->setEditable(true);
-    m_urlLabel->setBuddy(m_urlCombo);
-    connect(m_urlCombo, SIGNAL(editTextChanged(const QString &)),
-            this, SLOT(urlChanged(const QString &)));
-    outer->addWidget(m_urlCombo, 3, 1, 1, 2);
-
-    m_fileLabel = new QLabel(tr("&File:"));
-    outer->addWidget(m_fileLabel, 4, 0);
-
-    m_fileCombo = new QComboBox();
-    m_fileCombo->setEditable(true);
-    m_fileLabel->setBuddy(m_fileCombo);
-    connect(m_fileCombo, SIGNAL(editTextChanged(const QString &)),
-            this, SLOT(fileChanged(const QString &)));
-    outer->addWidget(m_fileCombo, 4, 1);
-    outer->setColumnStretch(1, 20);
-
-    m_browseButton = new QPushButton(tr("Browse..."));
-    outer->addWidget(m_browseButton, 4, 2);
-    connect(m_browseButton, SIGNAL(clicked()), this, SLOT(browse()));
-
-    outer->addItem(new QSpacerItem(2, 12), 5, 0);
-
-    QDialogButtonBox *bbox;
-    if (helpUrl != "") {
-        bbox = new QDialogButtonBox(QDialogButtonBox::Help |
-                                    QDialogButtonBox::Ok |
-                                    QDialogButtonBox::Cancel);
-    } else {
-        bbox = new QDialogButtonBox(QDialogButtonBox::Ok |
-                                    QDialogButtonBox::Cancel);
-    }        
-    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
-    connect(bbox, SIGNAL(rejected()), this, SLOT(reject()));
-    connect(bbox, SIGNAL(helpRequested()), this, SLOT(helpRequested()));
-    outer->addWidget(bbox, 6, 0, 1, 3);
-
-    m_okButton = bbox->button(QDialogButtonBox::Ok);
-    updateOkButton();
-
-    setMinimumWidth(480);
-}
-
-void
-MultiChoiceDialog::helpRequested()
-{
-    QDesktopServices::openUrl(m_helpUrl);
-}
-
-QString
-MultiChoiceDialog::getCurrentChoice()
-{
-    return m_currentChoice;
-}
-
-void
-MultiChoiceDialog::setCurrentChoice(QString c)
-{
-    m_currentChoice = c;
-    choiceChanged();
-}
-
-QString
-MultiChoiceDialog::getArgument()
-{
-    if (m_argTypes[m_currentChoice] == UrlArg ||
-        m_argTypes[m_currentChoice] == UrlToDirectoryArg) {
-        return m_urlCombo->currentText();
-    } else {
-        return m_fileCombo->currentText();
-    }
-}
-
-QString
-MultiChoiceDialog::getAdditionalArgument()
-{
-    if (m_argTypes[m_currentChoice] == UrlToDirectoryArg) {
-        return m_fileCombo->currentText();
-    } else {
-        return "";
-    }
-}
-
-void
-MultiChoiceDialog::addRecentArgument(QString id, QString arg,
-                                     bool additionalArgument)
-{
-    if (additionalArgument) {
-        RecentFiles(QString("Recent-%1-add").arg(id)).addFile(arg);
-    } else {
-        RecentFiles(QString("Recent-%1").arg(id)).addFile(arg);
-    }
-}
-
-void
-MultiChoiceDialog::addChoice(QString id, QString text,
-                             QString description, ArgType arg)
-{
-    bool first = (m_texts.empty());
-
-    m_texts[id] = text;
-    m_descriptions[id] = description;
-    m_argTypes[id] = arg;
-    
-    if (arg != NoArg) {
-        m_recentFiles[id] = QSharedPointer<RecentFiles>
-            (new RecentFiles(QString("Recent-%1").arg(id)));
-    }
-
-    SelectableLabel *cb = new SelectableLabel;
-    cb->setSelectedText(text);
-    cb->setUnselectedText(text);
-    cb->setMaximumWidth(270);
-
-    m_choiceLayout->addWidget(cb);
-    m_choiceButtons[cb] = id;
-
-    connect(cb, SIGNAL(selectionChanged()), this, SLOT(choiceChanged()));
-
-    if (first) {
-        m_currentChoice = id;
-        choiceChanged();
-    }
-}
-
-void
-MultiChoiceDialog::browse()
-{
-    QString origin = getArgument();
-
-    if (origin == "") {
-#ifdef Q_OS_WIN32
-        origin = "c:";
-#else
-        origin = QDir::homePath();
-#endif
-    }
-
-    QString path = origin;
-
-    if (m_argTypes[m_currentChoice] == DirectoryArg ||
-        m_argTypes[m_currentChoice] == UrlToDirectoryArg) {
-
-        path = QFileDialog::getExistingDirectory
-            (this, tr("Open Directory"), origin,
-             QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
-        if (path != QString()) {
-            m_fileCombo->lineEdit()->setText(path + QDir::separator());
-        }
-
-    } else {
-
-        path = QFileDialog::getOpenFileName
-            (this, tr("Open File"), origin);
-        if (path != QString()) {
-            m_fileCombo->lineEdit()->setText(path);
-        }
-    }
-}
-
-void
-MultiChoiceDialog::urlChanged(const QString &s)
-{
-    updateOkButton();
-}
-
-void
-MultiChoiceDialog::fileChanged(const QString &s)
-{
-    updateOkButton();
-}
-
-void
-MultiChoiceDialog::updateOkButton()
-{
-/* This doesn't work well
-    if (m_argTypes[m_currentChoice] != UrlToDirectoryArg) {
-        return;
-    }
-    QDir dirPath(m_fileCombo->currentText());
-    if (!dirPath.exists()) {
-        if (!dirPath.cdUp()) return;
-    }
-    QString url = m_urlCombo->currentText();
-    if (QRegExp("^\\w+://").indexIn(url) < 0) {
-        return;
-    }
-    QString urlDirName = url;
-    urlDirName.replace(QRegExp("^.*\\//.*\\/"), "");
-    if (urlDirName == "" || urlDirName == url) {
-        return;
-    }
-    m_fileCombo->lineEdit()->setText(dirPath.filePath(urlDirName));
-*/
-    if (m_argTypes[m_currentChoice] == UrlToDirectoryArg) {
-        m_okButton->setEnabled(getArgument() != "" &&
-                               getAdditionalArgument() != "");
-    } else {
-        m_okButton->setEnabled(getArgument() != "");
-    }
-}
-
-void
-MultiChoiceDialog::choiceChanged()
-{
-    DEBUG << "choiceChanged" << endl;
-
-    if (m_choiceButtons.empty()) return;
-
-    QString id = "";
-
-    QObject *s = sender();
-    QWidget *w = qobject_cast<QWidget *>(s);
-    if (w) id = m_choiceButtons[w];
-
-    if (id == m_currentChoice) return;
-    if (id == "") {
-        // Happens when this is called for the very first time, when
-        // m_currentChoice has been set to the intended ID but no
-        // button has actually been pressed -- then we need to
-        // initialise
-        id = m_currentChoice;
-    }
-
-    m_currentChoice = id;
-
-    foreach (QWidget *cw, m_choiceButtons.keys()) {
-        SelectableLabel *sl = qobject_cast<SelectableLabel *>(cw);
-        if (sl) {
-            sl->setSelected(m_choiceButtons[cw] == id);
-        }
-    }
-
-    m_descriptionLabel->setText(m_descriptions[id]);
-
-    m_fileLabel->hide();
-    m_fileCombo->hide();
-    m_browseButton->hide();
-    m_urlLabel->hide();
-    m_urlCombo->hide();
-
-    QSharedPointer<RecentFiles> rf = m_recentFiles[id];
-    m_fileCombo->clear();
-    m_urlCombo->clear();
-
-    switch (m_argTypes[id]) {
-        
-    case NoArg:
-        break;
-
-    case FileArg:
-        m_fileLabel->setText(tr("&File:"));
-        m_fileLabel->show();
-        m_fileCombo->show();
-        m_fileCombo->addItems(rf->getRecent());
-        m_browseButton->show();
-        break;
-
-    case DirectoryArg:
-        m_fileLabel->setText(tr("&Folder:"));
-        m_fileLabel->show();
-        m_fileCombo->show();
-        m_fileCombo->addItems(rf->getRecent());
-        m_browseButton->show();
-        break;
-
-    case UrlArg:
-        m_urlLabel->show();
-        m_urlCombo->show();
-        m_urlCombo->addItems(rf->getRecent());
-        break;
-
-    case UrlToDirectoryArg:
-        m_urlLabel->show();
-        m_urlCombo->show();
-        m_urlCombo->addItems(rf->getRecent());
-        m_fileLabel->setText(tr("&Folder:"));
-        m_fileLabel->show();
-        m_fileCombo->show();
-        m_fileCombo->lineEdit()->setText(QDir::homePath());
-        m_browseButton->show();
-        break;
-    }
-
-    updateOkButton();
-    adjustSize();
-}
-
-
--- a/multichoicedialog.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 MULTICHOICEDIALOG_H
-#define MULTICHOICEDIALOG_H
-
-#include "recentfiles.h"
-
-#include <QDialog>
-#include <QString>
-#include <QAbstractButton>
-#include <QMap>
-#include <QLabel>
-#include <QLineEdit>
-#include <QGridLayout>
-#include <QHBoxLayout>
-#include <QStackedWidget>
-#include <QSharedPointer>
-#include <QComboBox>
-
-class MultiChoiceDialog : public QDialog
-{
-    Q_OBJECT
-public:
-    explicit MultiChoiceDialog(QString title,
-                               QString heading,
-                               QString helpUrl = "",
-                               QWidget *parent = 0);
-
-    enum ArgType {
-        NoArg,
-        FileArg,
-        DirectoryArg,
-        UrlArg,
-        UrlToDirectoryArg
-    };
-
-    void addChoice(QString identifier, QString text,
-                   QString description, ArgType arg);
-
-    void setCurrentChoice(QString);
-    QString getCurrentChoice();
-    QString getArgument();
-    QString getAdditionalArgument();
-
-    static void addRecentArgument(QString identifier, QString name,
-                                  bool additionalArgument = false);
-
-private slots:
-    void choiceChanged();
-    void urlChanged(const QString &);
-    void fileChanged(const QString &);
-    void helpRequested();
-    void browse();
-
-private:
-    void updateOkButton();
-    
-    QString m_helpUrl;
-
-    QMap<QString, QString> m_texts;
-    QMap<QString, QString> m_descriptions;
-    QMap<QString, ArgType> m_argTypes;
-    QMap<QString, QSharedPointer<RecentFiles> > m_recentFiles;
-
-    QString m_currentChoice;
-    QMap<QWidget *, QString> m_choiceButtons;
-
-    QHBoxLayout *m_choiceLayout;
-    QLabel *m_descriptionLabel;
-    QLabel *m_fileLabel;
-    QComboBox *m_fileCombo;
-    QAbstractButton *m_browseButton;
-    QLabel *m_urlLabel;
-    QComboBox *m_urlCombo;
-    QAbstractButton *m_okButton;
-};
-
-#endif // MULTICHOICEDIALOG_H
--- a/panned.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,249 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "debug.h"
-
-#include <QScrollBar>
-#include <QWheelEvent>
-#include <QTimer>
-
-#include <cmath>
-
-#include <iostream>
-
-Panned::Panned() :
-    m_dragging(false)
-{
-    m_dragTimer = new QTimer(this);
-    m_dragTimerMs = 50;
-    connect(m_dragTimer, SIGNAL(timeout()), this, SLOT(dragTimerTimeout()));
-    setRenderHints(QPainter::Antialiasing |
-                   QPainter::TextAntialiasing);
-}
-
-void
-Panned::resizeEvent(QResizeEvent *ev)
-{
-    DEBUG << "Panned::resizeEvent()" << endl;
-
-    QPointF nearpt = mapToScene(0, 0);
-    QPointF farpt = mapToScene(width(), height());
-    QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y());
-    QRectF pr(nearpt, sz);
-
-    QGraphicsView::resizeEvent(ev);
-
-    if (pr != m_pannedRect) {
-        DEBUG << "Panned: setting panned rect to " << pr << endl;
-        m_pannedRect = pr;
-        centerOn(pr.center());
-        emit pannedRectChanged(pr);
-    }
-}
-
-void
-Panned::setScene(QGraphicsScene *s)
-{
-    if (!scene()) {
-        QGraphicsView::setScene(s);
-        return;
-    }
-
-    QPointF nearpt = mapToScene(0, 0);
-    QPointF farpt = mapToScene(width(), height());
-    QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y());
-    QRectF pr(nearpt, sz);
-
-    QGraphicsView::setScene(s);
-
-    DEBUG << "Panned::setScene: pr = " << pr << ", sceneRect = " << sceneRect() << endl;
-
-    if (scene() && sceneRect().intersects(pr)) {
-        DEBUG << "Panned::setScene: restoring old rect " << pr << endl;
-        m_pannedRect = pr;
-        centerOn(pr.center());
-        emit pannedRectChanged(pr);
-    }
-}
-
-void
-Panned::paintEvent(QPaintEvent *e)
-{
-    QGraphicsView::paintEvent(e);
-}
-
-void
-Panned::drawForeground(QPainter *paint, const QRectF &)
-{
-    QPointF nearpt = mapToScene(0, 0);
-    QPointF farpt = mapToScene(width(), height());
-    QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y());
-    QRectF pr(nearpt, sz);
-
-    if (pr != m_pannedRect) {
-        DEBUG << "Panned::drawForeground: visible rect " << pr << " differs from panned rect " << m_pannedRect << ", updating panned rect" <<endl;
-        if (pr.x() != m_pannedRect.x()) emit pannedContentsScrolled();
-        m_pannedRect = pr;
-        emit pannedRectChanged(pr);
-    }
-}
-
-void
-Panned::zoomIn()
-{
-    QMatrix m = matrix();
-    m.scale(1.0 / 1.1, 1.0 / 1.1);
-    setMatrix(m);
-}
-
-void
-Panned::zoomOut()
-{
-    QMatrix m = matrix();
-    m.scale(1.1, 1.1);
-    setMatrix(m);
-}
-
-void
-Panned::slotSetPannedRect(QRectF pr)
-{
-    centerOn(pr.center());
-//	setSceneRect(pr);
-//	m_pannedRect = pr;
-}
-
-void
-Panned::wheelEvent(QWheelEvent *ev)
-{
-    if (ev->modifiers() & Qt::ControlModifier) {
-        int d = ev->delta();
-        if (d > 0) {
-            while (d > 0) {
-                zoomOut();
-                d -= 120;
-            }
-        } else {
-            while (d < 0) {
-                zoomIn();
-                d += 120;
-            }
-        }
-    } else {
-        emit wheelEventReceived(ev);
-        QGraphicsView::wheelEvent(ev);
-    }
-}
-
-void
-Panned::slotEmulateWheelEvent(QWheelEvent *ev)
-{
-    QGraphicsView::wheelEvent(ev);
-}
-
-void
-Panned::mousePressEvent(QMouseEvent *ev)
-{
-    if (dragMode() != QGraphicsView::ScrollHandDrag ||
-        ev->button() != Qt::LeftButton) {
-        QGraphicsView::mousePressEvent(ev);
-        return;
-    }
-
-    DEBUG << "Panned::mousePressEvent: have left button in drag mode" << endl;
-
-    setDragMode(QGraphicsView::NoDrag);
-    QGraphicsView::mousePressEvent(ev);
-    setDragMode(QGraphicsView::ScrollHandDrag);
-
-    if (!ev->isAccepted()) {
-        ev->accept();
-        m_dragging = true;
-        m_lastDragPos = ev->pos();
-        m_lastOrigin = QPoint(horizontalScrollBar()->value(),
-                              verticalScrollBar()->value());
-        m_velocity = QPointF(0, 0);
-        m_dragTimer->start(m_dragTimerMs);
-    }
-
-}
-
-void
-Panned::mouseMoveEvent(QMouseEvent *ev)
-{
-    if (!m_dragging) {
-        QGraphicsView::mouseMoveEvent(ev);
-        return;
-    }
-    DEBUG << "Panned::mouseMoveEvent: dragging" << endl;
-    ev->accept();
-    QScrollBar *hBar = horizontalScrollBar();
-    QScrollBar *vBar = verticalScrollBar();
-    QPoint delta = ev->pos() - m_lastDragPos;
-    hBar->setValue(hBar->value() + (isRightToLeft() ? delta.x() : -delta.x()));
-    vBar->setValue(vBar->value() - delta.y());
-    m_lastDragPos = ev->pos();
-}
-
-void
-Panned::mouseReleaseEvent(QMouseEvent *ev)
-{
-    if (!m_dragging) {
-        QGraphicsView::mouseReleaseEvent(ev);
-        return;
-    }
-    DEBUG << "Panned::mouseReleaseEvent: dragging" << endl;
-    ev->accept();
-    m_dragging = false;
-}
-
-void
-Panned::dragTimerTimeout()
-{
-    QPoint origin = QPoint(horizontalScrollBar()->value(),
-                           verticalScrollBar()->value());
-    if (m_dragging) {
-        m_velocity = QPointF
-            (float(origin.x() - m_lastOrigin.x()) / m_dragTimerMs,
-             float(origin.y() - m_lastOrigin.y()) / m_dragTimerMs);
-        m_lastOrigin = origin;
-        DEBUG << "Panned::dragTimerTimeout: velocity = " << m_velocity << endl;
-    } else {
-        if (origin == m_lastOrigin) {
-            m_dragTimer->stop();
-        }
-        float x = m_velocity.x(), y = m_velocity.y();
-        if (fabsf(x) > 1.0/m_dragTimerMs) x = x * 0.9f;
-        else x = 0.f;
-        if (fabsf(y) > 1.0/m_dragTimerMs) y = y * 0.9f;
-        else y = 0.f;
-        m_velocity = QPointF(x, y);
-        DEBUG << "Panned::dragTimerTimeout: velocity adjusted to " << m_velocity << endl;
-        m_lastOrigin = origin;
-        //!!! need to store origin in floats
-        horizontalScrollBar()->setValue(m_lastOrigin.x() +
-                                        m_velocity.x() * m_dragTimerMs);
-        verticalScrollBar()->setValue(m_lastOrigin.y() +
-                                      m_velocity.y() * m_dragTimerMs);
-    }
-}
-
-void
-Panned::leaveEvent(QEvent *)
-{
-    emit mouseLeaves();
-}
--- a/panned.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 _PANNED_H_
-#define _PANNED_H_
-
-#include <QGraphicsView>
-
-class QWheelEvent;
-class QEvent;
-class QTimer;
-
-class Panned : public QGraphicsView
-{
-    Q_OBJECT
-
-public:
-    Panned();
-    virtual ~Panned() { }
-
-    virtual void setScene(QGraphicsScene *s);
-
-signals:
-    void pannedRectChanged(QRectF);
-    void wheelEventReceived(QWheelEvent *);
-    void pannedContentsScrolled();
-    void mouseLeaves();
-
-public slots:
-    void slotSetPannedRect(QRectF);
-    void slotEmulateWheelEvent(QWheelEvent *ev);
-
-    void zoomIn();
-    void zoomOut();
-
-private slots:
-    void dragTimerTimeout();
-
-protected:
-    QRectF m_pannedRect;
-
-    QPoint m_lastDragPos;
-    QPoint m_lastOrigin;
-    QPointF m_velocity;
-    bool m_dragging;
-    int m_dragTimerMs;
-    QTimer *m_dragTimer;
-
-    virtual void mousePressEvent(QMouseEvent *);
-    virtual void mouseMoveEvent(QMouseEvent *);
-    virtual void mouseReleaseEvent(QMouseEvent *);
-
-    virtual void paintEvent(QPaintEvent *);
-    virtual void resizeEvent(QResizeEvent *);
-    virtual void drawForeground(QPainter *, const QRectF &);
-    virtual void wheelEvent(QWheelEvent *);
-    virtual void leaveEvent(QEvent *);
-};
-
-#endif
-
--- a/panner.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,285 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "debug.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 |
-                         QGraphicsView::IndirectPainting);
-    setMouseTracking(true);
-    setInteractive(false);
-}
-
-void
-Panner::fit(QRectF r)
-{
-    Qt::AspectRatioMode m = Qt::IgnoreAspectRatio;
-    if (height() > width()) {
-        // Our panner is vertical; if the source is not tall,
-        // don't stretch it to fit in the height, it'd look weird
-        if (r.height() < height() * 2) {
-            m = Qt::KeepAspectRatio;
-        }
-    } else {
-        // Similarly, but horizontal
-        if (r.width() < width() * 2) {
-            m = Qt::KeepAspectRatio;
-        }
-    }
-    DEBUG << "Panner: fit mode " << m << endl;
-    fitInView(r, m);
-}
-
-void
-Panner::setScene(QGraphicsScene *s)
-{
-    if (scene()) {
-        disconnect(scene(), SIGNAL(changed(const QList<QRectF> &)),
-                   this, SLOT(slotSceneChanged(const QList<QRectF> &)));
-        disconnect(scene(), SIGNAL(sceneRectChanged(const QRectF &)),
-                   this, SLOT(slotSceneRectChanged(const QRectF &)));
-    }
-    QGraphicsView::setScene(s);
-    m_cache = QPixmap();
-    if (scene()) {
-        QRectF r = sceneRect();
-        DEBUG << "scene rect: " << r << ", my rect " << rect() << endl;
-        fit(r);
-        connect(scene(), SIGNAL(changed(const QList<QRectF> &)),
-                this, SLOT(slotSceneChanged(const QList<QRectF> &)));
-        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)));
-
-    connect(this, SIGNAL(zoomIn()),
-            p, SLOT(zoomIn()));
-
-    connect(this, SIGNAL(zoomOut()),
-            p, SLOT(zoomOut()));
-}
-
-void
-Panner::slotSetPannedRect(QRectF rect) 
-{
-    m_pannedRect = rect;
-    viewport()->update();
-}
-
-void
-Panner::resizeEvent(QResizeEvent *)
-{
-    DEBUG << "Panner::resizeEvent" << endl;
-    if (scene()) fit(sceneRect());
-    m_cache = QPixmap();
-}
-
-void
-Panner::slotSceneRectChanged(const QRectF &newRect)
-{
-    DEBUG << "Panner::slotSceneRectChanged" << endl;
-    if (!scene()) return; // spurious
-    fit(newRect);
-    m_cache = QPixmap();
-    viewport()->update();
-}
-
-void
-Panner::slotSceneChanged(const QList<QRectF> &)
-{
-    DEBUG << "Panner::slotSceneChanged" << endl;
-    if (!scene()) return; // spurious
-    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)
-{
-    DEBUG << "Panner::updateScene" << endl;
-//    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()) {
-
-        DEBUG << "Panner: cache size " << m_cache.size() << " != viewport size " << viewport()->size() << ": recreating cache" << endl;
-
-        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();
-
-        DEBUG << "done" << endl;
-    }
-
-    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);
-    m_pannedRect = 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)
-{
-    int d = e->delta();
-    if (d > 0) {
-        while (d > 0) {
-            emit zoomOut();
-            d -= 120;
-        }
-    } else {
-        while (d < 0) {
-            emit zoomIn();
-            d += 120;
-        }
-    }
-}
-
-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();
-}
-   
--- a/panner.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 _PANNER_H_
-#define _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 &);
-    void slotSceneChanged(const QList<QRectF> &);
-
-protected:
-    QRectF m_pannedRect;
-
-    void moveTo(QPoint);
-
-    void fit(QRectF);
-
-    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
-
--- a/recentfiles.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "recentfiles.h"
-
-#include <QFileInfo>
-#include <QSettings>
-#include <QRegExp>
-
-RecentFiles::RecentFiles(QString settingsGroup, size_t maxCount,
-                         bool ignoreTemporaries) :
-    m_settingsGroup(settingsGroup),
-    m_maxCount(maxCount),
-    m_ignoreTemporaries(ignoreTemporaries)
-{
-    read();
-}
-
-RecentFiles::~RecentFiles()
-{
-    // nothing
-}
-
-void
-RecentFiles::read()
-{
-    m_names.clear();
-    QSettings settings;
-    settings.beginGroup(m_settingsGroup);
-
-    for (size_t i = 0; i < 100; ++i) {
-        QString key = QString("recent-%1").arg(i);
-        QString name = settings.value(key, "").toString();
-        if (name == "") break;
-        if (i < m_maxCount) m_names.push_back(name);
-        else settings.setValue(key, "");
-    }
-
-    settings.endGroup();
-}
-
-void
-RecentFiles::write()
-{
-    QSettings settings;
-    settings.beginGroup(m_settingsGroup);
-
-    for (size_t i = 0; i < m_maxCount; ++i) {
-        QString key = QString("recent-%1").arg(i);
-        QString name = "";
-        if (i < m_names.size()) name = m_names[i];
-        settings.setValue(key, name);
-    }
-
-    settings.endGroup();
-}
-
-void
-RecentFiles::truncateAndWrite()
-{
-    while (m_names.size() > m_maxCount) {
-        m_names.pop_back();
-    }
-    write();
-}
-
-QStringList
-RecentFiles::getRecent() const
-{
-    QStringList names;
-    for (size_t i = 0; i < m_maxCount; ++i) {
-        if (i < m_names.size()) {
-            names.push_back(m_names[i]);
-        }
-    }
-    return names;
-}
-
-void
-RecentFiles::add(QString name)
-{
-    bool have = false;
-    for (size_t i = 0; i < m_names.size(); ++i) {
-        if (m_names[i] == name) {
-            have = true;
-            break;
-        }
-    }
-    
-    if (!have) {
-        m_names.push_front(name);
-    } else {
-        std::deque<QString> newnames;
-        newnames.push_back(name);
-        for (size_t i = 0; i < m_names.size(); ++i) {
-            if (m_names[i] == name) continue;
-            newnames.push_back(m_names[i]);
-        }
-        m_names = newnames;
-    }
-
-    truncateAndWrite();
-    emit recentChanged();
-}
-
-void
-RecentFiles::addFile(QString name)
-{
-    static QRegExp schemeRE("^[a-zA-Z]{2,5}://");
-    static QRegExp tempRE("[\\/][Tt]e?mp[\\/]");
-    if (schemeRE.indexIn(name) == 0) {
-        add(name);
-    } else {
-        QString absPath = QFileInfo(name).absoluteFilePath();
-        if (tempRE.indexIn(absPath) != -1) {
-            if (!m_ignoreTemporaries) {
-                add(absPath);
-            }
-        } else {
-            add(absPath);
-        }
-    }
-}
-
-
--- a/recentfiles.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 _RECENT_FILES_H_
-#define _RECENT_FILES_H_
-
-#include <QObject>
-#include <QString>
-#include <QStringList>
-#include <deque>
-
-/**
- * RecentFiles manages a list of the names of recently-used objects,
- * saving and restoring that list via QSettings.  The names do not
- * actually have to refer to files.
- */
-
-class RecentFiles : public QObject
-{
-    Q_OBJECT
-
-public:
-    /**
-     * Construct a RecentFiles object that saves and restores in the
-     * given QSettings group and truncates when the given count of
-     * strings is reached.
-     */
-    RecentFiles(QString settingsGroup = "RecentFiles",
-                size_t maxCount = 10,
-                bool ignoreTemporaries = true);
-    virtual ~RecentFiles();
-
-    QString getSettingsGroup() const { return m_settingsGroup; }
-
-    int getMaxCount() const { return m_maxCount; }
-
-    QStringList getRecent() const;
-
-    /**
-     * Add a name that should be treated as a literal string.
-     */
-    void add(QString name);
-    
-    /**
-     * Add a name that is known to be either a file path or a URL.  If
-     * it looks like a URL, add it literally; otherwise treat it as a
-     * file path and canonicalise it appropriately.  Also takes into
-     * account the preference for whether to include temporary files
-     * in the recent files menu: the file will not be added if the
-     * preference is set and the file appears to be a temporary one.
-     */
-    void addFile(QString name);
-
-signals:
-    void recentChanged();
-
-protected:
-    QString m_settingsGroup;
-    size_t m_maxCount;
-    bool m_ignoreTemporaries;
-
-    std::deque<QString> m_names;
-
-    void read();
-    void write();
-    void truncateAndWrite();
-};
-
-#endif
--- a/repositorydialog.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 "repositorydialog.h"
-
-RepositoryDialog::RepositoryDialog(QWidget *parent) :
-    QDialog(parent)
-{
-    setModal(true);
-    setWindowTitle(tr("Open Repository"));
-
-
-
-
-}
--- a/repositorydialog.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-
-    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 REPOSITORYDIALOG_H
-#define REPOSITORYDIALOG_H
-
-#include <QDialog>
-
-class RepositoryDialog : public QDialog
-{
-    Q_OBJECT
-
-public:
-    RepositoryDialog(QWidget *parent = 0);
-
-
-
-};
-
-#endif // REPOSITORYDIALOG_H
--- a/selectablelabel.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "selectablelabel.h"
-
-#include "debug.h"
-
-#include <iostream>
-#include <QApplication>
-
-SelectableLabel::SelectableLabel(QWidget *p) :
-    QLabel(p),
-    m_selected(false)
-{
-    setTextFormat(Qt::RichText);
-    setupStyle();
-    setOpenExternalLinks(true);
-}
-
-SelectableLabel::~SelectableLabel()
-{
-}
-
-void
-SelectableLabel::setUnselectedText(QString text)
-{
-    if (m_unselectedText == text) return;
-    m_unselectedText = text;
-    if (!m_selected) {
-        setText(m_unselectedText);
-        resize(sizeHint());
-    }
-}
-
-void
-SelectableLabel::setSelectedText(QString text)
-{
-    if (m_selectedText == text) return;
-    m_selectedText = text;
-    if (m_selected) {
-        setText(m_selectedText);
-        resize(sizeHint());
-    }
-}
-
-void
-SelectableLabel::setupStyle()
-{
-    QPalette palette = QApplication::palette();
-
-    setTextInteractionFlags(Qt::LinksAccessibleByKeyboard |
-                            Qt::LinksAccessibleByMouse |
-                            Qt::TextSelectableByMouse);
-
-    if (m_selected) {
-        setStyleSheet
-            (QString("QLabel { background: %1; border: 1px solid %2; padding: 7px } ")
-             .arg(palette.light().color().name())
-             .arg(palette.dark().color().name()));
-    } else {
-        setStyleSheet
-            (QString("QLabel { border: 0; padding: 7px } "));
-    }
-}    
-
-void
-SelectableLabel::setSelected(bool s)
-{
-    if (m_selected == s) return;
-    m_selected = s;
-    if (m_selected) {
-        setText(m_selectedText);
-    } else {
-        setText(m_unselectedText);
-    }
-    setupStyle();
-    parentWidget()->resize(parentWidget()->sizeHint());
-}
-
-void
-SelectableLabel::toggle()
-{
-    setSelected(!m_selected);
-}
-
-void
-SelectableLabel::mousePressEvent(QMouseEvent *e)
-{
-    m_swallowRelease = !m_selected;
-    setSelected(true);
-    QLabel::mousePressEvent(e);
-    emit selectionChanged();
-}
-
-void
-SelectableLabel::mouseDoubleClickEvent(QMouseEvent *e)
-{
-    QLabel::mouseDoubleClickEvent(e);
-    emit doubleClicked();
-}
-
-void
-SelectableLabel::mouseReleaseEvent(QMouseEvent *e)
-{
-    if (!m_swallowRelease) QLabel::mouseReleaseEvent(e);
-    m_swallowRelease = false;
-}
-
-void
-SelectableLabel::enterEvent(QEvent *)
-{
-//    std::cerr << "enterEvent" << std::endl;
-//    QPalette palette = QApplication::palette();
-//    palette.setColor(QPalette::Window, Qt::gray);
-//    setStyleSheet("background: gray");
-//    setPalette(palette);
-}
-
-void
-SelectableLabel::leaveEvent(QEvent *)
-{
-//    std::cerr << "leaveEvent" << std::endl;
-//    setStyleSheet("background: white");
-//    QPalette palette = QApplication::palette();
-//    palette.setColor(QPalette::Window, Qt::gray);
-//    setPalette(palette);
-}
--- a/selectablelabel.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 _SELECTABLE_LABEL_H_
-#define _SELECTABLE_LABEL_H_
-
-#include <QLabel>
-
-class SelectableLabel : public QLabel
-{
-    Q_OBJECT
-
-public:
-    SelectableLabel(QWidget *parent = 0);
-    virtual ~SelectableLabel();
-
-    void setSelectedText(QString);
-    void setUnselectedText(QString);
-
-    bool isSelected() const { return m_selected; }
-
-signals:
-    void selectionChanged();
-    void doubleClicked();
-
-public slots:
-    void setSelected(bool);
-    void toggle();
-
-protected:
-    virtual void mousePressEvent(QMouseEvent *e);
-    virtual void mouseReleaseEvent(QMouseEvent *e);
-    virtual void mouseDoubleClickEvent(QMouseEvent *e);
-    virtual void enterEvent(QEvent *);
-    virtual void leaveEvent(QEvent *);
-    void setupStyle();
-    QString m_selectedText;
-    QString m_unselectedText;
-    bool m_selected;
-    bool m_swallowRelease;
-};
-
-#endif
--- a/settingsdialog.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,469 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "settingsdialog.h"
-#include "common.h"
-#include "debug.h"
-
-#include <QGridLayout>
-#include <QGroupBox>
-#include <QDialogButtonBox>
-#include <QSettings>
-#include <QDir>
-#include <QFileDialog>
-#include <QMessageBox>
-
-QString
-SettingsDialog::m_installPath;
-
-SettingsDialog::SettingsDialog(QWidget *parent) :
-    QDialog(parent),
-    m_presentationChanged(false)
-{
-    setModal(true);
-    setWindowTitle(tr("Settings"));
-
-    QGridLayout *mainLayout = new QGridLayout;
-    setLayout(mainLayout);
-
-
-
-    QGroupBox *meBox = new QGroupBox(tr("User details"));
-    mainLayout->addWidget(meBox, 0, 0);
-    QGridLayout *meLayout = new QGridLayout;
-    meBox->setLayout(meLayout);
-
-    int row = 0;
-
-    meLayout->addWidget(new QLabel(tr("Name:")), row, 0);
-
-    m_nameEdit = new QLineEdit();
-    meLayout->addWidget(m_nameEdit, row++, 1);
-    
-    meLayout->addWidget(new QLabel(tr("Email address:")), row, 0);
-
-    m_emailEdit = new QLineEdit();
-    meLayout->addWidget(m_emailEdit, row++, 1);
-
-
-
-    QGroupBox *lookBox = new QGroupBox(tr("Presentation"));
-    mainLayout->addWidget(lookBox, 1, 0);
-    QGridLayout *lookLayout = new QGridLayout;
-    lookBox->setLayout(lookLayout);
-
-    row = 0;
-
-    m_showIconLabels = new QCheckBox(tr("Show labels on toolbar icons"));
-    lookLayout->addWidget(m_showIconLabels, row++, 0, 1, 2);
-
-    m_showExtraText = new QCheckBox(tr("Show long descriptions for file status headings"));
-    lookLayout->addWidget(m_showExtraText, row++, 0, 1, 2);
-    
-#ifdef NOT_IMPLEMENTED_YET
-    lookLayout->addWidget(new QLabel(tr("Place the work and history views")), row, 0);
-    m_workHistoryArrangement = new QComboBox();
-    m_workHistoryArrangement->addItem(tr("In separate tabs"));
-    m_workHistoryArrangement->addItem(tr("Side-by-side in a single pane"));
-    lookLayout->addWidget(m_workHistoryArrangement, row++, 1, Qt::AlignLeft);
-    lookLayout->setColumnStretch(1, 20);
-#endif
-
-    lookLayout->addWidget(new QLabel(tr("Label the history timeline with")), row, 0);
-    m_dateFormat = new QComboBox();
-    m_dateFormat->addItem(tr("Ages, for example \"5 weeks ago\""));
-    m_dateFormat->addItem(tr("Dates, for example \"2010-06-23\""));
-    lookLayout->addWidget(m_dateFormat, row++, 1, Qt::AlignLeft);
-    lookLayout->setColumnStretch(1, 20);
-    
-
-    QGroupBox *pathsBox = new QGroupBox(tr("System application locations"));
-    mainLayout->addWidget(pathsBox, 2, 0);
-    QGridLayout *pathsLayout = new QGridLayout;
-    pathsBox->setLayout(pathsLayout);
-
-    row = 0;
-
-    pathsLayout->addWidget(new QLabel(tr("Mercurial (hg) program:")), row, 0);
-
-    m_hgPathLabel = new QLineEdit();
-    pathsLayout->addWidget(m_hgPathLabel, row, 2);
-
-    QPushButton *browse = new QPushButton(tr("Browse..."));
-    pathsLayout->addWidget(browse, row++, 1);
-    connect(browse, SIGNAL(clicked()), this, SLOT(hgPathBrowse()));
-
-    pathsLayout->addWidget(new QLabel(tr("External diff program:")), row, 0);
-
-    m_diffPathLabel = new QLineEdit();
-    pathsLayout->addWidget(m_diffPathLabel, row, 2);
-
-    browse = new QPushButton(tr("Browse..."));
-    pathsLayout->addWidget(browse, row++, 1);
-    connect(browse, SIGNAL(clicked()), this, SLOT(diffPathBrowse()));
-    
-    pathsLayout->addWidget(new QLabel(tr("External file-merge program:")), row, 0);
-
-    m_mergePathLabel = new QLineEdit();
-    pathsLayout->addWidget(m_mergePathLabel, row, 2);
-
-    browse = new QPushButton(tr("Browse..."));
-    pathsLayout->addWidget(browse, row++, 1);
-    connect(browse, SIGNAL(clicked()), this, SLOT(mergePathBrowse()));
-
-    pathsLayout->addWidget(new QLabel(tr("External text editor:")), row, 0);
-
-    m_editPathLabel = new QLineEdit();
-    pathsLayout->addWidget(m_editPathLabel, row, 2);
-
-    browse = new QPushButton(tr("Browse..."));
-    pathsLayout->addWidget(browse, row++, 1);
-    connect(browse, SIGNAL(clicked()), this, SLOT(editPathBrowse()));
-
-    pathsLayout->addWidget(new QLabel(tr("EasyHg Mercurial extension:")), row, 0);
-
-    m_extensionPathLabel = new QLineEdit();
-    pathsLayout->addWidget(m_extensionPathLabel, row, 2);
-
-    browse = new QPushButton(tr("Browse..."));
-    pathsLayout->addWidget(browse, row++, 1);
-    connect(browse, SIGNAL(clicked()), this, SLOT(extensionPathBrowse()));
-
-    //!!! more info plz
-    m_useExtension = new QCheckBox(tr("Use EasyHg Mercurial extension"));
-    pathsLayout->addWidget(m_useExtension, row++, 2);
-
-
-    reset(); // loads current defaults from settings
-
-
-    QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok);
-    connect(bbox->addButton(tr("Restore defaults"), QDialogButtonBox::ResetRole),
-            SIGNAL(clicked()), this, SLOT(restoreDefaults()));
-    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
-    mainLayout->addWidget(bbox, 3, 0);
-    m_ok = bbox->button(QDialogButtonBox::Ok);
-}
-
-void
-SettingsDialog::hgPathBrowse()
-{
-    browseFor(tr("Mercurial program"), m_hgPathLabel);
-}
-
-void
-SettingsDialog::diffPathBrowse()
-{
-    browseFor(tr("External diff program"), m_diffPathLabel);
-}
-
-void
-SettingsDialog::mergePathBrowse()
-{
-    browseFor(tr("External file-merge program"), m_mergePathLabel);
-}
-
-void
-SettingsDialog::editPathBrowse()
-{
-    browseFor(tr("External text editor"), m_editPathLabel);
-}
-
-void
-SettingsDialog::extensionPathBrowse()
-{
-    browseFor(tr("EasyHg Mercurial extension"), m_extensionPathLabel);
-}
-
-void
-SettingsDialog::browseFor(QString title, QLineEdit *edit)
-{
-    QString origin = edit->text();
-
-    if (origin == "") {
-#ifdef Q_OS_WIN32
-        origin = "c:";
-#else
-        origin = QDir::homePath();
-#endif
-    }
-    
-    QString path = QFileDialog::getOpenFileName(this, title, origin);
-    if (path != QString()) {
-        edit->setText(path);
-    }
-}
-
-void
-SettingsDialog::restoreDefaults()
-{
-    if (QMessageBox::question
-        (this, tr("Restore default settings?"),
-         tr("<qt><b>Restore default settings?</b><br><br>Are you sure you want to reset all settings to their default values?"),
-         QMessageBox::Ok | QMessageBox::Cancel,
-         QMessageBox::Cancel) == QMessageBox::Ok) {
-        clear();
-        findDefaultLocations();
-        reset();
-    }
-}
-
-void
-SettingsDialog::findDefaultLocations(QString installPath)
-{
-    m_installPath = installPath;
-    findHgBinaryName();
-    findExtension();
-    findDiffBinaryName();
-    findMergeBinaryName();
-    findEditorBinaryName();
-}
-
-void
-SettingsDialog::findHgBinaryName()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    QString hg = settings.value("hgbinary", "").toString();
-    if (hg == "") {
-        hg = findInPath("hg", m_installPath, true);
-    }
-    if (hg != "") {
-        settings.setValue("hgbinary", hg);
-    }
-}
-
-QString
-SettingsDialog::getUnbundledExtensionFileName()
-{
-    QString home = QDir::homePath();
-    QString target = QString("%1/.easyhg").arg(home);
-    QString extpath = QString("%1/easyhg.py").arg(target);
-    return extpath;
-}
-
-void
-SettingsDialog::findExtension()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-
-    QString extpath = settings.value("extensionpath", "").toString();
-    if (extpath != "" || !QFile(extpath).exists()) {
-
-        extpath = getUnbundledExtensionFileName();
-
-        if (!QFile(extpath).exists()) {
-            extpath = findInPath("easyhg.py", m_installPath, false);
-        }
-    }
-
-    settings.setValue("extensionpath", extpath);
-}   
-
-void
-SettingsDialog::findDiffBinaryName()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    QString diff = settings.value("extdiffbinary", "").toString();
-    if (diff == "") {
-        QStringList bases;
-#ifdef Q_OS_WIN32
-        bases << "easyhg-extdiff.bat";
-#else
-        bases << "easyhg-extdiff.sh";
-#endif
-        bases << "kompare" << "kdiff3" << "meld";
-        bool found = false;
-        foreach (QString base, bases) {
-            diff = findInPath(base, m_installPath, true);
-            if (diff != "") {
-                found = true;
-                break;
-            }
-        }
-        if (found) {
-            settings.setValue("extdiffbinary", diff);
-        }
-    }
-}
-
-void
-SettingsDialog::findMergeBinaryName()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    if (settings.contains("mergebinary")) {
-        return;
-    }
-    QString merge;
-    QStringList bases;
-#ifdef Q_OS_WIN32
-    bases << "easyhg-merge.bat";
-#else
-    bases << "easyhg-merge.sh";
-#endif
-    // NB it's not a good idea to add other tools here, as command
-    // line argument ordering varies.  Configure them through hgrc
-    // instead
-    bool found = false;
-    foreach (QString base, bases) {
-        merge = findInPath(base, m_installPath, true);
-        if (merge != "") {
-            found = true;
-            break;
-        }
-    }
-    if (found) {
-        settings.setValue("mergebinary", merge);
-    }
-}
-
-void
-SettingsDialog::findEditorBinaryName()
-{
-    QSettings settings;
-    settings.beginGroup("Locations");
-    QString editor = settings.value("editorbinary", "").toString();
-    if (editor == "") {
-        QStringList bases;
-        bases
-#if defined Q_OS_WIN32
-            << "wordpad.exe"
-            << "C:\\Program Files\\Windows NT\\Accessories\\wordpad.exe"
-            << "notepad.exe"
-#elif defined Q_OS_MAC
-            << "/Applications/TextEdit.app/Contents/MacOS/TextEdit"
-#else
-            << "gedit" << "kate"
-#endif
-            ;
-        bool found = false;
-        foreach (QString base, bases) {
-            editor = findInPath(base, m_installPath, true);
-            if (editor != "") {
-                found = true;
-                break;
-            }
-        }
-        if (found) {
-            settings.setValue("editorbinary", editor);
-        }
-    }
-}
-
-void
-SettingsDialog::clear()
-{
-    // Clear everything that has a default setting
-    DEBUG << "SettingsDialog::clear" << endl;
-    QSettings settings;
-    settings.beginGroup("Presentation");
-    settings.remove("showiconlabels");
-    settings.remove("showhelpfultext");
-    settings.endGroup();
-    settings.beginGroup("Locations");
-    settings.remove("hgbinary");
-    settings.remove("extdiffbinary");
-    settings.remove("mergebinary");
-    settings.remove("editorbinary");
-    settings.remove("extensionpath");
-    settings.endGroup();
-    settings.beginGroup("General");
-    settings.remove("useextension");
-    settings.endGroup();
-}
-
-void
-SettingsDialog::reset()
-{
-    DEBUG << "SettingsDialog::reset" << endl;
-    QSettings settings;
-    settings.beginGroup("User Information");
-    m_nameEdit->setText(settings.value("name", getUserRealName()).toString());
-    m_emailEdit->setText(settings.value("email").toString());
-    settings.endGroup();
-    settings.beginGroup("Presentation");
-    m_showIconLabels->setChecked(settings.value("showiconlabels", true).toBool());
-    m_showExtraText->setChecked(settings.value("showhelpfultext", true).toBool());
-#ifdef NOT_IMPLEMENTED_YET
-    m_workHistoryArrangement->setCurrentIndex(settings.value("workhistoryarrangement", 0).toInt());
-#endif
-    m_dateFormat->setCurrentIndex(settings.value("dateformat", 0).toInt());
-    settings.endGroup();
-    settings.beginGroup("Locations");
-    m_hgPathLabel->setText(settings.value("hgbinary").toString());
-    m_diffPathLabel->setText(settings.value("extdiffbinary").toString());
-    m_mergePathLabel->setText(settings.value("mergebinary").toString());
-    m_editPathLabel->setText(settings.value("editorbinary").toString());
-    m_extensionPathLabel->setText(settings.value("extensionpath").toString());
-    settings.endGroup();
-    settings.beginGroup("General");
-    m_useExtension->setChecked(settings.value("useextension", true).toBool());
-    settings.endGroup();
-}
-
-void
-SettingsDialog::accept()
-{
-    DEBUG << "SettingsDialog::accept" << endl;
-    QSettings settings;
-    settings.beginGroup("User Information");
-    settings.setValue("name", m_nameEdit->text());
-    settings.setValue("email", m_emailEdit->text());
-    settings.endGroup();
-    settings.beginGroup("Presentation");
-    bool b;
-    b = m_showIconLabels->isChecked();
-    if (b != settings.value("showiconlabels", true)) {
-        settings.setValue("showiconlabels", b);
-        m_presentationChanged = true;
-    }
-    b = m_showExtraText->isChecked();
-    if (b != settings.value("showhelpfultext", true)) {
-        settings.setValue("showhelpfultext", b);
-        m_presentationChanged = true;
-    }
-    int i;
-#ifdef NOT_IMPLEMENTED_YET
-    i = m_workHistoryArrangement->currentIndex();
-    if (i != settings.value("workhistoryarrangement", 0)) {
-        settings.setValue("workhistoryarrangement", i);
-        m_presentationChanged = true;
-    }
-#endif
-    i = m_dateFormat->currentIndex();
-    if (i != settings.value("dateformat", 0)) {
-        settings.setValue("dateformat", i);
-        m_presentationChanged = true;
-    }
-    settings.endGroup();
-    settings.beginGroup("Locations");
-    settings.setValue("hgbinary", m_hgPathLabel->text());
-    settings.setValue("extdiffbinary", m_diffPathLabel->text());
-    settings.setValue("mergebinary", m_mergePathLabel->text());
-    settings.setValue("editorbinary", m_editPathLabel->text());
-    settings.setValue("extensionpath", m_extensionPathLabel->text());
-    settings.endGroup();
-    settings.beginGroup("General");
-    settings.setValue("useextension", m_useExtension->isChecked());
-    settings.endGroup();
-    QDialog::accept();
-}
-
-    
--- a/settingsdialog.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 SETTINGS_DIALOG_H
-#define SETTINGS_DIALOG_H
-
-#include <QDialog>
-#include <QLineEdit>
-#include <QLabel>
-#include <QPushButton>
-#include <QCheckBox>
-#include <QComboBox>
-
-class SettingsDialog : public QDialog
-{
-    Q_OBJECT
-
-public:
-    SettingsDialog(QWidget *parent = 0);
-
-    bool presentationChanged() {
-        return m_presentationChanged;
-    }
-
-    static void findDefaultLocations(QString installPath = m_installPath);
-    static QString getUnbundledExtensionFileName();
-    
-private slots:
-    void hgPathBrowse();
-    void diffPathBrowse();
-    void mergePathBrowse();
-    void editPathBrowse();
-    void extensionPathBrowse();
-
-    void accept();
-    void reset();
-    void clear();
-    void restoreDefaults();
-
-private:
-    QLineEdit *m_nameEdit;
-    QLineEdit *m_emailEdit;
-    QLineEdit *m_hgPathLabel;
-    QLineEdit *m_diffPathLabel;
-    QLineEdit *m_mergePathLabel;
-    QLineEdit *m_editPathLabel;
-
-    QCheckBox *m_useExtension;
-    QLineEdit *m_extensionPathLabel;
-
-    QCheckBox *m_showIconLabels;
-    QCheckBox *m_showExtraText;
-    QComboBox *m_dateFormat;
-#ifdef NOT_IMPLEMENTED_YET
-    QComboBox *m_workHistoryArrangement;
-#endif
-
-    QPushButton *m_ok;
-
-    bool m_presentationChanged;
-
-    void browseFor(QString, QLineEdit *);
-
-    static void findHgBinaryName();
-    static void findExtension();
-    static void findDiffBinaryName();
-    static void findMergeBinaryName();
-    static void findEditorBinaryName();
-
-    static QString m_installPath;
-};
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/annotatedialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,94 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 "annotatedialog.h"
+#include "common.h"
+#include "colourset.h"
+#include "debug.h"
+
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QTableWidget>
+#include <QHeaderView>
+#include <QGridLayout>
+
+AnnotateDialog::AnnotateDialog(QWidget *w, QString text) :
+    QDialog(w)
+{
+    setMinimumWidth(800);
+    setMinimumHeight(500);
+
+    text.replace("\r\n", "\n");
+    QStringList lines = text.split("\n");
+
+    QGridLayout *layout = new QGridLayout;
+    QTableWidget *table = new QTableWidget;
+
+    QRegExp annotateLineRE = QRegExp("^([^:]+) ([a-z0-9]{12}) ([0-9-]+): (.*)$");
+
+    table->setRowCount(lines.size());
+    table->setColumnCount(4);
+    table->horizontalHeader()->setStretchLastSection(true);
+    table->verticalHeader()->setDefaultSectionSize
+	(table->verticalHeader()->fontMetrics().height() + 2);
+
+    QStringList labels;
+    labels << tr("User") << tr("Revision") << tr("Date") << tr("Content");
+    table->setHorizontalHeaderLabels(labels);
+
+    table->setShowGrid(false);
+
+    QFont monofont("Monospace");
+    monofont.setStyleHint(QFont::TypeWriter);
+
+    int row = 0;
+
+    foreach (QString line, lines) {
+	if (annotateLineRE.indexIn(line) == 0) {
+	    QStringList items = annotateLineRE.capturedTexts();
+	    QString id = items[2];
+	    QColor colour = ColourSet::instance()->getColourFor(id);
+	    QColor bg = QColor::fromHsv(colour.hue(),
+					30,
+					230);
+	    // note items[0] is the whole match, so we want 1-4
+	    for (int col = 0; col+1 < items.size(); ++col) {
+		QString item = items[col+1];
+		if (col == 0) item = item.trimmed();
+		QTableWidgetItem *wi = new QTableWidgetItem(item);
+		wi->setFlags(Qt::ItemIsEnabled);
+		wi->setBackground(bg);
+		if (col == 3) { // id, content
+		    wi->setFont(monofont);
+		}
+		table->setItem(row, col, wi);
+	    }
+	} else {
+	    DEBUG << "AnnotateDialog: Failed to match RE in line: " << line << " at row " << row << endl;
+	}
+	++row;
+    }
+
+    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
+    connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
+
+    layout->addWidget(table, 0, 0);
+    layout->addWidget(bb, 1, 0);
+
+    setLayout(layout);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/annotatedialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,31 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 ANNOTATE_DIALOG_H
+#define ANNOTATE_DIALOG_H
+
+#include <QDialog>
+
+class AnnotateDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    AnnotateDialog(QWidget *parent, QString output);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changeset.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,113 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "changeset.h"
+#include "common.h"
+#include "debug.h"
+
+#include <QVariant>
+
+Changeset::Changeset(const LogEntry &e) :
+    m_closed(false)
+{
+    foreach (QString key, e.keys()) {
+        if (key == "parents") {
+            QStringList parents = e.value(key).split
+                (" ", QString::SkipEmptyParts);
+            setParents(parents);
+        } else if (key == "tag") {
+            QStringList tags = e.value(key).split
+                (" ", QString::SkipEmptyParts);
+            setTags(tags);
+        } else if (key == "bookmarks") {
+            QStringList bmarks = e.value(key).split
+                (" ", QString::SkipEmptyParts);
+            setBookmarks(bmarks);
+        } else if (key == "timestamp") {
+            setTimestamp(e.value(key).split(" ")[0].toULongLong());
+        } else if (key == "changeset") {
+            setId(e.value(key));
+        } else {
+            setProperty(key.toLocal8Bit().data(), e.value(key));
+        }
+    }
+}
+
+QString Changeset::getLogTemplate()
+{
+    return "id: {rev}:{node|short}\\nauthor: {author}\\nbranch: {branches}\\ntag: {tags}\\nbookmarks: {bookmarks}\\ndatetime: {date|isodate}\\ntimestamp: {date|hgdate}\\nage: {date|age}\\nparents: {parents}\\ncomment: {desc|json}\\n\\n";
+}
+
+QString Changeset::formatHtml()
+{
+    QString description;
+    QString rowTemplate = "<tr><td><b>%1</b>&nbsp;</td><td>%2</td></tr>";
+
+    description = "<qt><table border=0>";
+
+//    DEBUG << "comment is " << comment() << endl;
+
+    QString c = comment().trimmed();
+    c = c.replace(QRegExp("^\""), "");
+    c = c.replace(QRegExp("\"$"), "");
+    c = c.replace("\\\"", "\"");
+    c = xmlEncode(c);
+    c = c.replace("\\n", "<br>");
+
+    QStringList propNames, propTexts;
+    
+    propNames << "id"
+	      << "author"
+	      << "datetime"
+	      << "branch"
+	      << "tags"
+	      << "bookmarks"
+	      << "comment";
+
+    propTexts << QObject::tr("Identifier:")
+	      << QObject::tr("Author:")
+	      << QObject::tr("Date:")
+	      << QObject::tr("Branch:")
+	      << QObject::tr("Tags:")
+	      << QObject::tr("Bookmarks:")
+	      << QObject::tr("Comment:");
+
+    for (int i = 0; i < propNames.size(); ++i) {
+	QString prop = propNames[i];
+	QString value;
+        if (prop == "id") {
+            value = hashOf(id());
+        } else if (prop == "comment") {
+            value = c;
+        } else if (prop == "tags") {
+            value = tags().join(" ");
+        } else if (prop == "bookmarks") {
+            value = bookmarks().join(" ");
+        } else {
+	    value = xmlEncode(property(prop.toLocal8Bit().data()).toString());
+	}
+	if (value != "") {
+	    description += rowTemplate
+		.arg(xmlEncode(propTexts[i]))
+		.arg(value);
+	}
+    }
+
+    description += "</table></qt>";
+
+    return description;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changeset.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,167 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 CHANGESET_H
+#define CHANGESET_H
+
+#include <QObject>
+#include <QString>
+#include <QStringList>
+#include <QList>
+#include <QSharedPointer>
+
+#include "logparser.h"
+
+class Changeset;
+
+typedef QList<Changeset *> Changesets;
+
+class Changeset : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged STORED true);
+    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged STORED true);
+    Q_PROPERTY(QString branch READ branch WRITE setBranch NOTIFY branchChanged STORED true);
+    Q_PROPERTY(QStringList tags READ tags WRITE setTags NOTIFY tagsChanged STORED true);
+    Q_PROPERTY(QStringList bookmarks READ bookmarks WRITE setBookmarks NOTIFY bookmarksChanged STORED true);
+    Q_PROPERTY(QString datetime READ datetime WRITE setDatetime NOTIFY datetimeChanged STORED true);
+    Q_PROPERTY(qulonglong timestamp READ timestamp WRITE setTimestamp NOTIFY timestampChanged STORED true);
+    Q_PROPERTY(QString age READ age WRITE setAge NOTIFY ageChanged STORED true);
+    Q_PROPERTY(QStringList parents READ parents WRITE setParents NOTIFY parentsChanged STORED true);
+    Q_PROPERTY(QStringList children READ children WRITE setChildren NOTIFY childrenChanged STORED true);
+    Q_PROPERTY(QString comment READ comment WRITE setComment NOTIFY commentChanged STORED true);
+
+public:
+    Changeset() : QObject() { }
+    explicit Changeset(const LogEntry &e);
+
+    QString id() const { return m_id; }
+    QString author() const { return m_author; }
+    QString branch() const { return m_branch; }
+    QStringList tags() const { return m_tags; }
+    QStringList bookmarks() const { return m_bookmarks; }
+    QString datetime() const { return m_datetime; }
+    qulonglong timestamp() const { return m_timestamp; }
+    QString age() const { return m_age; }
+    QStringList parents() const { return m_parents; }
+    QString comment() const { return m_comment; }
+
+    /**
+     * The children property is not obtained from Hg, but set in
+     * Grapher::layout() based on reported parents
+     */
+    QStringList children() const { return m_children; }
+
+    /**
+     * The closed property is not obtained from Hg, but set in
+     * Grapher::layout() based on traversing closed branch graphs
+     */
+    bool closed() const { return m_closed; }
+
+    int number() const {
+        return id().split(':')[0].toInt();
+    }
+
+    QString authorName() const {
+	QString a = author();
+	return a.replace(QRegExp("\\s*<[^>]*>"), "");
+    }
+
+    QString date() const {
+        return datetime().split(' ')[0];
+    }
+
+    bool isOnBranch(QString branch) {
+        QString b = m_branch;
+        if (branch == "") branch = "default";
+        if (b == "") b = "default";
+        if (branch == b) return true;
+        return false;
+    }
+
+    static QString hashOf(QString id) {
+        return id.split(':')[1];
+    }
+
+    static QStringList getIds(Changesets csets) {
+        QStringList ids;
+        foreach (Changeset *cs, csets) ids.push_back(cs->id());
+        return ids;
+    }
+
+    static Changesets parseChangesets(QString logText) {
+        Changesets csets;
+        LogList log = LogParser(logText).parse();
+        foreach (LogEntry e, log) {
+            csets.push_back(new Changeset(e));
+        }
+        return csets;
+    }
+
+    static QString getLogTemplate();
+
+    QString formatHtml();
+    
+signals:
+    void idChanged(QString id);
+    void authorChanged(QString author);
+    void branchChanged(QString branch);
+    void tagsChanged(QStringList tags);
+    void bookmarksChanged(QStringList bookmarks);
+    void datetimeChanged(QString datetime);
+    void timestampChanged(qulonglong timestamp);
+    void ageChanged(QString age);
+    void parentsChanged(QStringList parents);
+    void childrenChanged(QStringList children);
+    void closedChanged(bool closed);
+    void commentChanged(QString comment);
+
+public slots:
+    void setId(QString id) { m_id = id; emit idChanged(id); }
+    void setAuthor(QString author) { m_author = author; emit authorChanged(author); }
+    void setBranch(QString branch) { m_branch = branch; emit branchChanged(branch); }
+    void setTags(QStringList tags) { m_tags = tags; emit tagsChanged(tags); }
+    void addTag(QString tag) { m_tags.push_back(tag); emit tagsChanged(m_tags); }
+    void setBookmarks(QStringList bmarks) { m_bookmarks = bmarks; emit bookmarksChanged(bmarks); }
+    void addBookmark(QString b) { m_bookmarks.push_back(b); emit bookmarksChanged(m_bookmarks); }
+    void setDatetime(QString datetime) { m_datetime = datetime; emit datetimeChanged(datetime); }
+    void setTimestamp(qulonglong timestamp) { m_timestamp = timestamp; emit timestampChanged(timestamp); }
+    void setAge(QString age) { m_age = age; emit ageChanged(age); }
+    void setParents(QStringList parents) { m_parents = parents; emit parentsChanged(parents); }
+    void setChildren(QStringList children) { m_children = children; emit childrenChanged(m_children); }
+    void addChild(QString child) { m_children.push_back(child); emit childrenChanged(m_children); }
+    void setClosed(bool closed) { m_closed = closed; emit closedChanged(closed); }
+    void setComment(QString comment) { m_comment = comment; emit commentChanged(comment); }
+
+private:
+    QString m_id;
+    QString m_author;
+    QString m_branch;
+    QStringList m_tags;
+    QStringList m_bookmarks;
+    QString m_datetime;
+    qulonglong m_timestamp;
+    QString m_age;
+    QStringList m_parents;
+    QStringList m_children;
+    bool m_closed;
+    QString m_comment;
+};
+
+
+#endif // CHANGESET_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetdetailitem.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,120 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "changesetdetailitem.h"
+#include "changeset.h"
+#include "textabbrev.h"
+#include "colourset.h"
+#include "debug.h"
+#include "common.h"
+
+#include <QTextDocument>
+#include <QPainter>
+#include <QAbstractTextDocumentLayout>
+
+ChangesetDetailItem::ChangesetDetailItem(Changeset *cs) :
+    m_changeset(cs), m_doc(0)
+{
+    m_font = QFont();
+    m_font.setPixelSize(11);
+    m_font.setBold(false);
+    m_font.setItalic(false);
+
+    makeDocument();
+}
+
+ChangesetDetailItem::~ChangesetDetailItem()
+{
+    delete m_doc;
+}
+
+QRectF
+ChangesetDetailItem::boundingRect() const
+{
+    int w = 350;
+    m_doc->setTextWidth(w);
+    return QRectF(-10, -10, w + 10, m_doc->size().height() + 10);
+}
+
+QVariant
+ChangesetDetailItem::itemChange(GraphicsItemChange c, const QVariant &v)
+{
+    if (c == ItemVisibleHasChanged) {
+        bool visible = v.toBool();
+        DEBUG << "ChangesetDetailItem::itemChange: visible = " << visible << endl;
+        if (visible && scene()) {
+            ensureVisible();
+        }
+    }
+    return v;
+}
+
+void
+ChangesetDetailItem::paint(QPainter *paint,
+			   const QStyleOptionGraphicsItem *,
+			   QWidget *)
+{
+    paint->save();
+    
+    ColourSet *colourSet = ColourSet::instance();
+    QColor branchColour = colourSet->getColourFor(m_changeset->branch());
+
+    QTransform t = paint->worldTransform();
+    float scale = std::min(t.m11(), t.m22());
+
+    if (scale < 0.1) {
+	paint->setPen(QPen(branchColour, 0));
+    } else {
+	paint->setPen(QPen(branchColour, 2));
+    }
+    
+    int width = 350;
+    m_doc->setTextWidth(width);
+    int height = m_doc->size().height();
+
+    QRectF r(0.5, 0.5, width - 1, height - 1);
+    paint->setBrush(Qt::white);
+    paint->drawRoundedRect(r, 10, 10);
+
+    if (scale < 0.1) {
+	paint->restore();
+	return;
+    }
+
+    // little triangle connecting to its "owning" changeset item
+    paint->setBrush(branchColour);
+    QVector<QPointF> pts;
+    pts.push_back(QPointF(0, height/3 - 5));
+    pts.push_back(QPointF(0, height/3 + 5));
+    pts.push_back(QPointF(-10, height/3));
+    pts.push_back(QPointF(0, height/3 - 5));
+    paint->drawPolygon(QPolygonF(pts));
+
+    m_doc->drawContents(paint, r);
+
+    paint->restore();
+}
+
+void
+ChangesetDetailItem::makeDocument()
+{
+    delete m_doc;
+    m_doc = new QTextDocument;
+    m_doc->setHtml(m_changeset->formatHtml());
+    m_doc->setDefaultFont(m_font);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetdetailitem.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,47 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 CHANGESETDETAILITEM_H
+#define CHANGESETDETAILITEM_H
+
+#include <QGraphicsItem>
+#include <QFont>
+
+class Changeset;
+
+class ChangesetDetailItem : public QGraphicsItem
+{
+public:
+    ChangesetDetailItem(Changeset *cs);
+    virtual ~ChangesetDetailItem();
+
+    virtual QRectF boundingRect() const;
+    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
+
+    Changeset *getChangeset() { return m_changeset; }
+
+private:
+    QFont m_font;
+    Changeset *m_changeset;
+    QTextDocument *m_doc;
+
+    QVariant itemChange(GraphicsItemChange, const QVariant &);
+
+    void makeDocument();
+};
+
+#endif // CHANGESETDETAILITEM_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetitem.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,605 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "changesetitem.h"
+#include "changesetscene.h"
+#include "changesetdetailitem.h"
+#include "changeset.h"
+#include "textabbrev.h"
+#include "colourset.h"
+#include "debug.h"
+
+#include <QPainter>
+#include <QGraphicsScene>
+#include <QGraphicsSceneMouseEvent>
+#include <QMenu>
+#include <QAction>
+#include <QLabel>
+#include <QWidgetAction>
+#include <QApplication>
+#include <QClipboard>
+
+QImage *ChangesetItem::m_star = 0;
+
+ChangesetItem::ChangesetItem(Changeset *cs) :
+    m_changeset(cs), m_detail(0), m_detailVisible(false),
+    m_showBranch(false), m_column(0), m_row(0), m_wide(false),
+    m_current(false), m_closing(false), m_new(false), m_searchMatches(false)
+{
+    m_font = QFont();
+    m_font.setPixelSize(11);
+    m_font.setBold(false);
+    m_font.setItalic(false);
+    setCursor(Qt::ArrowCursor);
+
+    if (!m_star) m_star = new QImage(":images/star.png");
+}
+
+ChangesetItem::~ChangesetItem()
+{
+    if (m_detail && !m_detailVisible) delete m_detail;
+}
+
+QString
+ChangesetItem::getId()
+{
+    return m_changeset->id();
+}
+
+QRectF
+ChangesetItem::boundingRect() const
+{
+    int w = 100;
+    if (m_wide) w = 180;
+    return QRectF(-((w-50)/2 - 1), -30, w - 3, 90);
+}
+
+void
+ChangesetItem::showDetail()
+{
+    if (m_detailVisible) return;
+    if (!m_detail) {
+        m_detail = new ChangesetDetailItem(m_changeset);
+        m_detail->setZValue(zValue() + 1);
+    }
+    scene()->addItem(m_detail);
+    int w = 100;
+    if (m_wide) w = 180;
+    if (isMerge() || isClosingCommit()) w = 60;
+    int h = 80;
+    m_detail->setPos(x() + (w + 50) / 2 + 10 + 0.5,
+                     y() - (m_detail->boundingRect().height() - h) / 3 + 0.5);
+    m_detailVisible = true;
+    emit detailShown();
+}    
+
+void
+ChangesetItem::hideDetail()
+{
+    if (!m_detailVisible) return;
+    scene()->removeItem(m_detail);
+    m_detailVisible = false;
+    emit detailHidden();
+}    
+
+bool
+ChangesetItem::matchSearchText(QString text)
+{
+    m_searchText = text;
+    m_searchMatches = false;
+    if (m_showBranch) {
+        m_searchMatches = (m_changeset->branch().contains
+                           (text, Qt::CaseInsensitive));
+    }
+    if (!m_searchMatches) {
+        m_searchMatches = (m_changeset->comment().contains
+                           (text, Qt::CaseInsensitive));
+    }
+    return m_searchMatches;
+}
+
+void
+ChangesetItem::mousePressEvent(QGraphicsSceneMouseEvent *e)
+{
+    DEBUG << "ChangesetItem::mousePressEvent" << endl;
+    if (e->button() == Qt::LeftButton) {
+        if (m_detailVisible) {
+            hideDetail();
+        } else {
+            showDetail();
+        }
+    }
+}
+
+void
+ChangesetItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *)
+{
+    if (m_detail) {
+        hideDetail();
+    }
+
+    m_parentDiffActions.clear();
+    m_summaryActions.clear();
+
+    QMenu *menu = new QMenu;
+    QLabel *label = new QLabel(tr("<qt><b>&nbsp;Revision: </b>%1</qt>")
+                               .arg(Changeset::hashOf(m_changeset->id())));
+    QWidgetAction *wa = new QWidgetAction(menu);
+    wa->setDefaultWidget(label);
+    menu->addAction(wa);
+    menu->addSeparator();
+
+    QAction *copyId = menu->addAction(tr("Copy identifier to clipboard"));
+    connect(copyId, SIGNAL(triggered()), this, SLOT(copyIdActivated()));
+
+    QAction *stat = menu->addAction(tr("Summarise changes"));
+    connect(stat, SIGNAL(triggered()), this, SLOT(showSummaryActivated()));
+
+    menu->addSeparator();
+
+    QStringList parents = m_changeset->parents();
+
+    QString leftId, rightId;
+    bool havePositions = false;
+
+    if (parents.size() > 1) {
+        ChangesetScene *cs = dynamic_cast<ChangesetScene *>(scene());
+        if (cs && parents.size() == 2) {
+            ChangesetItem *i0 = cs->getItemById(parents[0]);
+            ChangesetItem *i1 = cs->getItemById(parents[1]);
+            if (i0 && i1) {
+                if (i0->x() < i1->x()) {
+                    leftId = parents[0];
+                    rightId = parents[1];
+                } else {
+                    leftId = parents[1];
+                    rightId = parents[0];
+                }
+                havePositions = true;
+            }
+        }
+    }
+
+    if (parents.size() > 1) {
+        if (havePositions) {
+            
+            QAction *diff = menu->addAction(tr("Diff to left parent"));
+            connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated()));
+            m_parentDiffActions[diff] = leftId;
+            
+            diff = menu->addAction(tr("Diff to right parent"));
+            connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated()));
+            m_parentDiffActions[diff] = rightId;
+
+        } else {
+
+            foreach (QString parentId, parents) {
+                QString text = tr("Diff to parent %1").arg(Changeset::hashOf(parentId));
+                QAction *diff = menu->addAction(text);
+                connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated()));
+                m_parentDiffActions[diff] = parentId;
+            }
+        }
+
+    } else {
+
+        QAction *diff = menu->addAction(tr("Diff to parent"));
+        connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated()));
+    }
+
+    QAction *diffCurrent = menu->addAction(tr("Diff to current working folder"));
+    connect(diffCurrent, SIGNAL(triggered()), this, SLOT(diffToCurrentActivated()));
+
+    menu->addSeparator();
+
+    QAction *update = menu->addAction(tr("Update to this revision"));
+    connect(update, SIGNAL(triggered()), this, SLOT(updateActivated()));
+
+    QAction *merge = menu->addAction(tr("Merge from here to current"));
+    connect(merge, SIGNAL(triggered()), this, SLOT(mergeActivated()));
+
+    menu->addSeparator();
+
+    QAction *branch = menu->addAction(tr("Start new branch..."));
+    branch->setEnabled(m_current);
+    connect(branch, SIGNAL(triggered()), this, SLOT(newBranchActivated()));
+
+    QAction *closebranch = menu->addAction(tr("Close branch..."));
+    closebranch->setEnabled(m_current);
+    connect(closebranch, SIGNAL(triggered()), this, SLOT(closeBranchActivated()));
+
+    QAction *tag = menu->addAction(tr("Add tag..."));
+    connect(tag, SIGNAL(triggered()), this, SLOT(tagActivated()));
+
+    ungrabMouse();
+
+    menu->exec(QCursor::pos());
+}
+
+void
+ChangesetItem::copyIdActivated()
+{
+    QClipboard *clipboard = QApplication::clipboard();
+    clipboard->setText(Changeset::hashOf(m_changeset->id()));
+}
+
+void ChangesetItem::diffToParentActivated()
+{
+    QAction *a = qobject_cast<QAction *>(sender());
+    QString parentId;
+    if (m_parentDiffActions.contains(a)) {
+        parentId = m_parentDiffActions[a];
+        DEBUG << "ChangesetItem::diffToParentActivated: specific parent " 
+              << parentId << " selected" << endl;
+    } else {
+        parentId = m_changeset->parents()[0];
+        DEBUG << "ChangesetItem::diffToParentActivated: "
+              << "no specific parent selected, using first parent "
+              << parentId << endl;
+    }
+
+    emit diffToParent(getId(), parentId);
+}
+
+void ChangesetItem::showSummaryActivated()
+{
+    emit showSummary(m_changeset);
+}
+
+void ChangesetItem::updateActivated() { emit updateTo(getId()); }
+void ChangesetItem::diffToCurrentActivated() { emit diffToCurrent(getId()); }
+void ChangesetItem::mergeActivated() { emit mergeFrom(getId()); }
+void ChangesetItem::tagActivated() { emit tag(getId()); }
+void ChangesetItem::newBranchActivated() { emit newBranch(getId()); }
+void ChangesetItem::closeBranchActivated() { emit closeBranch(getId()); }
+
+void
+ChangesetItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
+{
+    if (isClosingCommit() || isMerge()) {
+        paintSimple(paint);
+    } else {
+        paintNormal(paint);
+    }
+}
+
+bool
+ChangesetItem::isMerge() const
+{
+    return (m_changeset && m_changeset->parents().size() > 1);
+}
+
+bool
+ChangesetItem::isClosed() const
+{
+    return (m_changeset && m_changeset->closed());
+}
+
+void
+ChangesetItem::paintNormal(QPainter *paint)
+{
+    paint->save();
+    
+    int alpha = 255;
+    if (isClosed()) alpha = 90;
+
+    ColourSet *colourSet = ColourSet::instance();
+    QColor branchColour = colourSet->getColourFor(m_changeset->branch());
+    QColor userColour = colourSet->getColourFor(m_changeset->author());
+
+    branchColour.setAlpha(alpha);
+    userColour.setAlpha(alpha);
+
+    QFont f(m_font);
+
+    QTransform t = paint->worldTransform();
+    float scale = std::min(t.m11(), t.m22());
+
+    bool showText = (scale >= 0.2);
+    bool showProperLines = (scale >= 0.1);
+
+    if (m_searchText != "") {
+        if (m_searchMatches) {
+            userColour = QColor("#008400");
+            showProperLines = true;
+            showText = true;
+        } else {
+            branchColour = Qt::gray;
+            userColour = Qt::gray;
+        }
+    }
+
+    if (!showProperLines) {
+	paint->setPen(QPen(branchColour, 0));
+    } else {
+	paint->setPen(QPen(branchColour, 2));
+    }
+	
+    paint->setFont(f);
+    QFontMetrics fm(f);
+    int fh = fm.height();
+
+    int width = 100;
+    if (m_wide) width = 180;
+    int x0 = -((width - 50) / 2 - 1);
+
+    int textwid = width - 7;
+
+    QString comment;
+    QStringList lines;
+    int lineCount = 3;
+
+    if (showText) {
+    
+        comment = m_changeset->comment().trimmed();
+        comment = comment.replace("\\n", " ");
+        comment = comment.replace(QRegExp("^\"\\s*\\**\\s*"), "");
+        comment = comment.replace(QRegExp("\"$"), "");
+        comment = comment.replace("\\\"", "\"");
+
+        comment = TextAbbrev::abbreviate(comment, fm, textwid,
+                                         TextAbbrev::ElideEnd, "...", 3);
+        // abbreviate() changes this (ouch!), restore it
+        textwid = width - 5;
+
+        lines = comment.split('\n');
+        lineCount = lines.size();
+
+        if (lineCount < 2) lineCount = 2;
+    }
+
+    int height = (lineCount + 1) * fh + 2;
+    QRectF r(x0, 0, width - 3, height);
+
+    QColor textColour = Qt::black;
+    textColour.setAlpha(alpha);
+
+    if (m_showBranch && showText) {
+	// write branch name
+        paint->save();
+	f.setBold(true);
+	paint->setFont(f);
+	paint->setPen(QPen(branchColour));
+	QString branch = m_changeset->branch();
+        if (branch == "") branch = "default";
+	int wid = width - 3;
+	branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid);
+	paint->drawText(x0, -fh + fm.ascent() - 4, branch);
+	f.setBold(false);
+        paint->restore();
+    }
+
+    QStringList bookmarks = m_changeset->bookmarks();
+    if (!bookmarks.empty() && showText) {
+        QString bmText = bookmarks.join(" ").trimmed();
+        int bw = fm.width(bmText);
+        int bx = x0 + width - bw - 14;
+        if (m_current) bx = bx - fh*1.5 + 3;
+        paint->save();
+        paint->setPen(QPen(branchColour, 2));
+//        paint->setBrush(QBrush(Qt::white));
+        paint->setBrush(QBrush(branchColour));
+        paint->drawRoundedRect(QRectF(bx, -fh - 4, bw + 4, fh * 2), 5, 5);
+        paint->setPen(QPen(Qt::white));
+        paint->drawText(bx + 2, -fh + fm.ascent() - 4, bmText);
+        paint->restore();
+    }
+
+    if (showProperLines) {
+
+        if (m_new) {
+            paint->setBrush(QColor(255, 255, 220));
+        } else {
+            paint->setBrush(Qt::white);
+        }            
+
+        if (m_current) {
+            paint->drawRoundedRect(QRectF(x0 - 4, -4, width + 5, height + 8),
+                                   10, 10);
+            if (m_new) {
+                paint->save();
+                paint->setPen(Qt::yellow);
+                paint->setBrush(Qt::NoBrush);
+                paint->drawRoundedRect(QRectF(x0 - 2, -2, width + 1, height + 4),
+                                       10, 10);
+                paint->restore();
+            }
+        }
+    }
+
+    if (!showText) {
+        paint->drawRoundedRect(r, 7, 7);
+	paint->restore();
+	return;
+    }
+
+    paint->save();
+    paint->setPen(Qt::NoPen);
+    paint->drawRoundedRect(r, 7, 7);
+    paint->setBrush(QBrush(userColour));
+    paint->drawRoundedRect(QRectF(x0 + 0.5, 0.5, width - 4, fh - 0.5), 7, 7);
+    paint->drawRect(QRectF(x0 + 0.5, fh/2.0, width - 4, fh/2.0));
+    paint->restore();
+
+    paint->setPen(QPen(Qt::white));
+
+    QString person = TextAbbrev::abbreviate(m_changeset->authorName(),
+                                            fm, textwid);
+    paint->drawText(x0 + 3, fm.ascent(), person);
+
+    paint->setPen(QPen(textColour));
+
+    QStringList tags = m_changeset->tags();
+    if (!tags.empty()) {
+        QStringList nonTipTags;
+        foreach (QString t, tags) {
+            // I'm not convinced that showing the tip tag really
+            // works; I think perhaps it confuses as much as it
+            // illuminates.  But also, our current implementation
+            // doesn't interact well with it because it moves -- it's
+            // the one thing that can actually (in normal use) change
+            // inside an existing changeset record even during an
+            // incremental update
+            if (t != "tip") nonTipTags.push_back(t);
+        }
+        if (!nonTipTags.empty()) {
+            QString tagText = nonTipTags.join(" ").trimmed();
+            int tw = fm.width(tagText);
+            paint->fillRect(QRectF(x0 + width - 8 - tw, 1, tw + 4, fh - 1),
+                            QBrush(Qt::yellow));
+            paint->drawText(x0 + width - 6 - tw, fm.ascent(), tagText);
+        }
+    }
+
+    paint->setPen(QPen(branchColour, 2));
+    paint->setBrush(Qt::NoBrush);
+    paint->drawRoundedRect(r, 7, 7);
+
+    if (m_current && showProperLines) {
+        paint->setRenderHint(QPainter::SmoothPixmapTransform, true);
+        int starSize = fh * 1.5;
+        paint->drawImage(QRectF(x0 + width - starSize,
+                                -fh, starSize, starSize),
+                         *m_star);
+    }
+
+    paint->setFont(f);
+
+    if (m_searchMatches) paint->setPen(userColour);
+
+    for (int i = 0; i < lines.size(); ++i) {
+	paint->drawText(x0 + 3, i * fh + fh + fm.ascent(), lines[i].trimmed());
+    }
+
+    paint->restore();
+}
+
+void
+ChangesetItem::paintSimple(QPainter *paint)
+{
+    paint->save();
+
+    int alpha = 255;
+    if (isClosed()) alpha = 90;
+    
+    ColourSet *colourSet = ColourSet::instance();
+    QColor branchColour = colourSet->getColourFor(m_changeset->branch());
+    QColor userColour = colourSet->getColourFor(m_changeset->author());
+
+    branchColour.setAlpha(alpha);
+    userColour.setAlpha(alpha);
+
+    QFont f(m_font);
+
+    QTransform t = paint->worldTransform();
+    float scale = std::min(t.m11(), t.m22());
+    if (scale > 1.0) {
+	int ps = int((f.pixelSize() / scale) + 0.5);
+	if (ps < 8) ps = 8;
+	f.setPixelSize(ps);
+    }
+
+    bool showProperLines = (scale >= 0.1);
+
+    if (!showProperLines) {
+	paint->setPen(QPen(branchColour, 0));
+    } else {
+	paint->setPen(QPen(branchColour, 2));
+    }
+	
+    paint->setFont(f);
+    QFontMetrics fm(f);
+    int fh = fm.height();
+    int size = fh * 2;
+    int x0 = -size/2 + 25;
+
+    if (m_new) {
+        paint->setBrush(QColor(255, 255, 220));
+    } else {
+        paint->setBrush(Qt::white);
+    }
+
+    if (showProperLines) {
+
+        if (m_current) {
+            if (isClosingCommit()) {
+                paint->drawRect(QRectF(x0 - 4, fh - 4, size + 8, size + 8));
+            } else {
+                paint->drawEllipse(QRectF(x0 - 4, fh - 4, size + 8, size + 8));
+            }
+            
+            if (m_new) {
+                paint->save();
+                paint->setPen(Qt::yellow);
+                paint->setBrush(Qt::NoBrush);
+                if (isClosingCommit()) {
+                    paint->drawRect(QRectF(x0 - 2, fh - 2, size + 4, size + 4));
+                } else {
+                    paint->drawEllipse(QRectF(x0 - 2, fh - 2, size + 4, size + 4));
+                }
+                paint->restore();
+            }
+        }
+    }
+
+    if (isClosingCommit()) {
+        paint->drawRect(QRectF(x0, fh, size, size));
+    } else {
+        paint->drawEllipse(QRectF(x0, fh, size, size));
+    }
+
+    if (m_showBranch) {
+	// write branch name
+        paint->save();
+	f.setBold(true);
+	paint->setFont(f);
+	paint->setPen(QPen(branchColour));
+	QString branch = m_changeset->branch();
+        if (branch == "") branch = "default";
+	int wid = size * 3;
+	branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid);
+	paint->drawText(-wid/2 + 25, fm.ascent() - 4, branch);
+	f.setBold(false);
+        paint->restore();
+    }
+
+    QStringList tags = m_changeset->tags();
+    if (!tags.empty()) {
+        QStringList nonTipTags;
+        foreach (QString t, tags) {
+            if (t != "tip") nonTipTags.push_back(t);
+        }
+        if (!nonTipTags.empty()) {
+            QString tagText = nonTipTags.join(" ").trimmed();
+            int tw = fm.width(tagText);
+            paint->fillRect(QRectF(x0 + size/2 + 2, 0, tw + 4, fh - 1),
+                            QBrush(Qt::yellow));
+            paint->drawText(x0 + size/2 + 4, fm.ascent() - 1, tagText);
+        }
+    }
+
+    if (m_current && showProperLines) {
+        paint->setRenderHint(QPainter::SmoothPixmapTransform, true);
+        int starSize = fh * 1.5;
+        paint->drawImage(QRectF(x0 + size - starSize/2,
+                                0, starSize, starSize),
+                         *m_star);
+    }
+
+    paint->restore();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetitem.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,130 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 CHANGESETITEM_H
+#define CHANGESETITEM_H
+
+#include <QGraphicsObject>
+#include <QFont>
+
+class Changeset;
+class ChangesetDetailItem;
+
+class QAction;
+class QImage;
+
+class ChangesetItem : public QGraphicsObject
+{
+    Q_OBJECT
+
+public:
+    ChangesetItem(Changeset *cs);
+    ~ChangesetItem();
+
+    virtual QRectF boundingRect() const;
+    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
+
+    Changeset *getChangeset() { return m_changeset; }
+    QString getId();
+
+    int column() const { return m_column; }
+    int row() const { return m_row; }
+    void setColumn(int c) { m_column = c; setX(c * 100); }
+    void setRow(int r) { m_row = r; setY(r * 90); }
+
+    bool isWide() const { return m_wide; }
+    void setWide(bool w) { m_wide = w; }
+
+    bool isCurrent() const { return m_current; }
+    void setCurrent(bool c) { m_current = c; }
+
+    // Closed is true if this changeset is on a closed branch
+    bool isClosed() const;
+
+    // Closing is true if this changeset is the commit that closed its
+    // branch (i.e. is at the end of a closed branch)
+    bool isClosingCommit() const { return m_closing; }
+    void setClosingCommit(bool c) { m_closing = c; }
+
+    bool isNew() const { return m_new; }
+    void setNew(bool n) { m_new = n; }
+
+    bool showBranch() const { return m_showBranch; }
+    void setShowBranch(bool s) { m_showBranch = s; }
+
+    bool matchSearchText(QString text); // return true and records the match if it matches
+
+signals:
+    void detailShown();
+    void detailHidden();
+
+    void updateTo(QString);
+    void diffToCurrent(QString);
+    void diffToParent(QString child, QString parent);
+    void showSummary(Changeset *);
+    void mergeFrom(QString);
+    void newBranch(QString);
+    void closeBranch(QString);
+    void tag(QString);
+
+public slots:
+    void showDetail();
+    void hideDetail();
+
+private slots:
+    void copyIdActivated();
+    void updateActivated();
+    void diffToParentActivated();
+    void showSummaryActivated();
+    void diffToCurrentActivated();
+    void mergeActivated();
+    void tagActivated();
+    void newBranchActivated();
+    void closeBranchActivated();
+
+protected:
+    virtual void mousePressEvent(QGraphicsSceneMouseEvent *);
+    virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *);
+
+private:
+    void activateMenu();
+
+    QFont m_font;
+    Changeset *m_changeset;
+    ChangesetDetailItem *m_detail;
+    bool m_detailVisible;
+    bool m_showBranch;
+    int m_column;
+    int m_row;
+    bool m_wide;
+    bool m_current;
+    bool m_closing;
+    bool m_new;
+    QString m_searchText;
+    bool m_searchMatches;
+
+    QMap<QAction *, QString> m_parentDiffActions;
+    QMap<QAction *, QString> m_summaryActions;
+
+    static QImage *m_star;
+
+    bool isMerge() const;
+    void paintNormal(QPainter *);
+    void paintSimple(QPainter *);
+};
+
+#endif // CHANGESETITEM_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetscene.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,172 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "changesetscene.h"
+#include "changesetitem.h"
+#include "uncommitteditem.h"
+#include "debug.h"
+
+#include <QPainter>
+
+
+ChangesetScene::ChangesetScene()
+    // Supply a non-NULL but trivial scene rect to inhibit automatic
+    // updates from QGraphicsScene, because we will set the rect
+    // explicitly in itemAddCompleted
+    : QGraphicsScene(QRectF(0, 0, 1, 1)), m_detailShown(0)
+{
+}
+
+void
+ChangesetScene::addChangesetItem(ChangesetItem *item)
+{
+    addItem(item);
+
+    connect(item, SIGNAL(detailShown()),
+            this, SLOT(changesetDetailShown()));
+
+    connect(item, SIGNAL(detailHidden()),
+            this, SLOT(changesetDetailHidden()));
+
+    connect(item, SIGNAL(updateTo(QString)),
+            this, SIGNAL(updateTo(QString)));
+
+    connect(item, SIGNAL(diffToCurrent(QString)),
+            this, SIGNAL(diffToCurrent(QString)));
+
+    connect(item, SIGNAL(diffToParent(QString, QString)),
+            this, SIGNAL(diffToParent(QString, QString)));
+
+    connect(item, SIGNAL(showSummary(Changeset *)),
+            this, SIGNAL(showSummary(Changeset *)));
+
+    connect(item, SIGNAL(mergeFrom(QString)),
+            this, SIGNAL(mergeFrom(QString)));
+
+    connect(item, SIGNAL(newBranch(QString)),
+            this, SIGNAL(newBranch(QString)));
+
+    connect(item, SIGNAL(closeBranch(QString)),
+            this, SIGNAL(closeBranch(QString)));
+
+    connect(item, SIGNAL(tag(QString)),
+            this, SIGNAL(tag(QString)));
+}
+
+void
+ChangesetScene::addUncommittedItem(UncommittedItem *item)
+{
+    addItem(item);
+    
+    connect(item, SIGNAL(commit()),
+            this, SIGNAL(commit()));
+    
+    connect(item, SIGNAL(revert()),
+            this, SIGNAL(revert()));
+    
+    connect(item, SIGNAL(diff()),
+            this, SIGNAL(diffWorkingFolder()));
+
+    connect(item, SIGNAL(showSummary()),
+            this, SIGNAL(showSummary()));
+
+    connect(item, SIGNAL(showWork()),
+            this, SIGNAL(showWork()));
+
+    connect(item, SIGNAL(newBranch()),
+            this, SIGNAL(newBranch()));
+
+    connect(item, SIGNAL(noBranch()),
+            this, SIGNAL(noBranch()));
+
+}
+
+void
+ChangesetScene::addDateRange(QString label, int minrow, int nrows, bool even)
+{
+    DateRange dr;
+    dr.label = label;
+    dr.minrow = minrow;
+    dr.nrows = nrows;
+    dr.even = even;
+    m_dateRanges[minrow] = dr;
+}
+
+void
+ChangesetScene::itemAddCompleted()
+{
+    connect(this, SIGNAL(changed(const QList<QRectF> &)),
+            this, SLOT(recalculateSceneRect()));
+    recalculateSceneRect();
+}
+
+void
+ChangesetScene::recalculateSceneRect()
+{
+    QRectF existingSr = sceneRect();
+
+    QRectF r = itemsBoundingRect();
+    float minwidth = 300; //!!!
+    DEBUG << "ChangesetScene::recalculateSceneRect: minwidth = " << minwidth
+          << ", r = " << r << endl;
+    if (r.width() < minwidth) {
+        float edgediff = (minwidth - r.width()) / 2;
+        r.setLeft(r.left() - edgediff);
+        r.setRight(r.right() + edgediff);
+    }
+    DEBUG << "ChangesetScene::recalculateSceneRect: r now is " << r << endl;
+
+    setSceneRect(r.united(existingSr));
+}
+
+void
+ChangesetScene::changesetDetailShown()
+{
+    ChangesetItem *csi = qobject_cast<ChangesetItem *>(sender());
+    if (!csi) return;
+
+    if (m_detailShown && m_detailShown != csi) {
+	m_detailShown->hideDetail();
+    }
+    m_detailShown = csi;
+}
+
+void
+ChangesetScene::changesetDetailHidden()
+{
+    m_detailShown = 0;
+    itemAddCompleted();
+}
+
+void
+ChangesetScene::drawBackground(QPainter *paint, const QRectF &rect)
+{
+    QGraphicsScene::drawBackground(paint, rect);
+}
+        
+
+ChangesetItem *
+ChangesetScene::getItemById(QString id)
+{
+    foreach (QGraphicsItem *it, items()) {
+        ChangesetItem *csit = dynamic_cast<ChangesetItem *>(it);
+        if (csit && csit->getId() == id) return csit;
+    }
+    return 0;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetscene.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,85 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 CHANGESETSCENE_H
+#define CHANGESETSCENE_H
+
+#include <QGraphicsScene>
+#include <QMap>
+
+class ChangesetItem;
+class Changeset;
+class UncommittedItem;
+
+class ChangesetScene : public QGraphicsScene
+{
+    Q_OBJECT
+
+public:
+    ChangesetScene();
+
+    void addChangesetItem(ChangesetItem *item);
+    void addUncommittedItem(UncommittedItem *item);
+
+    void addDateRange(QString label, int minrow, int nrows, bool even);
+
+    struct DateRange {
+        QString label;
+        int minrow;
+        int nrows;
+        bool even;
+    };
+
+    typedef QMap<int, DateRange> DateRanges; // key is minrow
+    DateRanges getDateRanges() const { return m_dateRanges; }
+
+    void itemAddCompleted(); // recalculate scene rect
+
+    ChangesetItem *getItemById(QString id); // Slow: traversal required
+
+signals:
+    void commit();
+    void revert();
+    void diffWorkingFolder();
+    void showSummary();
+    void showWork();
+    void newBranch();
+    void noBranch();
+
+    void updateTo(QString id);
+    void diffToParent(QString id, QString parent);
+    void showSummary(Changeset *);
+    void diffToCurrent(QString id);
+    void mergeFrom(QString id);
+    void newBranch(QString id);
+    void closeBranch(QString id);
+    void tag(QString id);
+
+private slots:
+    void changesetDetailShown();
+    void changesetDetailHidden();
+    void recalculateSceneRect();
+
+protected:
+    void drawBackground(QPainter *, const QRectF &);
+
+private:
+    ChangesetItem *m_detailShown;
+    DateRanges m_dateRanges;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetview.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,89 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "changesetview.h"
+#include "changesetscene.h"
+#include "colourset.h"
+#include "debug.h"
+
+#include <QScrollBar>
+
+ChangesetView::ChangesetView() :
+    Panned()
+{
+    connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
+	    this, SLOT(horizontalScrollHappened()));
+}
+
+void
+ChangesetView::horizontalScrollHappened()
+{
+    DEBUG << "ChangesetView::horizontalScrollHappened" << endl;
+    invalidateScene(rect(), QGraphicsScene::BackgroundLayer);
+    viewport()->update();
+}
+
+void
+ChangesetView::drawBackground(QPainter *paint, const QRectF &rect)
+{
+    DEBUG << "ChangesetView::drawBackground" << endl;
+
+    ChangesetScene *cs = qobject_cast<ChangesetScene *>(scene());
+
+    if (!cs) {
+	QGraphicsView::drawBackground(paint, rect);
+	return;
+    }
+
+    DEBUG << "ChangesetView::drawBackground: have scene" << endl;
+
+    ChangesetScene::DateRanges ranges = cs->getDateRanges();
+
+    paint->setClipRect(rect);
+
+    DEBUG << "clip rect is " << rect << endl;
+
+    paint->save();
+    QFont f(paint->font());
+    f.setBold(true);
+    paint->setFont(f);
+
+    float x = mapToScene(0, 0).x();
+    float w = mapToScene(width(), 0).x() - x;
+    float px = mapToScene(5, 0).x();
+
+    QBrush oddBrush(QColor::fromRgb(250, 250, 250));
+    QBrush evenBrush(QColor::fromRgb(240, 240, 240));
+
+    //!!! todo: select only the ranges actually within range!
+    
+    for (ChangesetScene::DateRanges::const_iterator i = ranges.begin();
+         i != ranges.end(); ++i) {
+
+	ChangesetScene::DateRange range = i.value();
+
+        QRectF r = QRectF(x, range.minrow * 90 - 25,
+			  w, range.nrows * 90).normalized();
+
+	paint->fillRect(r, range.even ? evenBrush : oddBrush);
+        paint->drawText(px, range.minrow * 90 - 10, range.label);
+    }
+
+    paint->restore();
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/changesetview.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,37 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 CHANGESETVIEW_H
+#define CHANGESETVIEW_H
+
+#include "panned.h"
+
+class ChangesetView : public Panned
+{
+    Q_OBJECT
+    
+public:
+    ChangesetView();
+
+private slots:
+    void horizontalScrollHappened();
+
+protected:
+    void drawBackground(QPainter *, const QRectF &);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/clickablelabel.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,90 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _CLICKABLE_LABEL_H_
+#define _CLICKABLE_LABEL_H_
+
+#include "squeezedlabel.h"
+
+#include <QMouseEvent>
+
+class ClickableLabel : public SqueezedLabel
+{
+    Q_OBJECT
+
+    Q_PROPERTY(bool mouseUnderline READ mouseUnderline WRITE setMouseUnderline)
+
+public:
+    ClickableLabel(const QString &text, QWidget *parent = 0) :
+        SqueezedLabel(text, parent),
+	m_naturalText(text)
+    { }
+
+    ClickableLabel(QWidget *parent = 0) :
+	SqueezedLabel(parent)
+    { }
+
+    ~ClickableLabel()
+    { }
+
+    void setText(const QString &t) {
+	m_naturalText = t;
+	SqueezedLabel::setText(t);
+    }
+
+    bool mouseUnderline() const {
+	return m_mouseUnderline;
+    }
+
+    void setMouseUnderline(bool mu) {
+	m_mouseUnderline = mu;
+	if (mu) {
+	    setTextFormat(Qt::RichText);
+	    setCursor(Qt::PointingHandCursor);
+	}
+    }
+
+signals:
+    void clicked();
+
+protected:
+    virtual void enterEvent(QEvent *) {
+	if (m_mouseUnderline) {
+	    SqueezedLabel::setText(tr("<u>%1</u>").arg(m_naturalText));
+	}
+    }
+
+    virtual void leaveEvent(QEvent *) {
+	if (m_mouseUnderline) {
+	    SqueezedLabel::setText(m_naturalText);
+	}
+    }
+
+    virtual void mousePressEvent(QMouseEvent *ev) {
+        if (ev->button() == Qt::LeftButton) {
+            emit clicked();
+        } else {
+            SqueezedLabel::mousePressEvent(ev);
+        }
+    }
+
+private:
+    bool m_mouseUnderline;
+    QString m_naturalText;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colourset.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,54 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "colourset.h"
+
+ColourSet
+ColourSet::m_instance;
+
+ColourSet::ColourSet() { }
+
+ColourSet *
+ColourSet::instance()
+{
+    return &m_instance;
+}
+
+QColor
+ColourSet::getColourFor(QString n)
+{
+    if (m_defaultNames.contains(n)) return Qt::black;
+    if (m_colours.contains(n)) return m_colours[n];
+
+    QColor c;
+
+    if (m_colours.empty()) {
+	c = QColor::fromHsv(0, 200, 150);
+    } else {
+        int hue = m_lastColour.hue() - 130;
+        if (hue < 0) hue += 360;
+	c = QColor::fromHsv(hue, 200, 150);
+    }
+
+    m_colours[n] = c;
+    m_lastColour = c;
+    return c;
+}
+
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/colourset.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,45 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _COLOURSET_H_
+#define _COLOURSET_H_
+
+#include <QSet>
+#include <QMap>
+#include <QColor>
+#include <QString>
+
+class ColourSet
+{
+public:
+    void clearDefaultNames() { m_defaultNames.clear(); }
+    void addDefaultName(QString n) { m_defaultNames.insert(n); }
+
+    QColor getColourFor(QString n);
+
+    static ColourSet *instance();
+
+private:
+    ColourSet();
+    QSet<QString> m_defaultNames;
+    QMap<QString, QColor> m_colours;
+    QColor m_lastColour;
+
+    static ColourSet m_instance;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/common.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,354 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "common.h"
+#include "debug.h"
+
+#include <QFileInfo>
+#include <QProcessEnvironment>
+#include <QStringList>
+#include <QDir>
+#include <QRegExp>
+
+#include <sys/types.h>
+
+#ifdef Q_OS_WIN32
+#define _WIN32_WINNT 0x0500
+#define SECURITY_WIN32 
+#include <windows.h>
+#include <security.h>
+#include <process.h>
+#else
+#include <errno.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#endif
+
+QString findInPath(QString name, QString installPath, bool executableRequired)
+{
+    bool found = false;
+    if (name != "") {
+        if (name[0] != '/'
+#ifdef Q_OS_WIN32
+            && (QRegExp("^\\w:").indexIn(name) != 0)
+#endif
+            ) {
+#ifdef Q_OS_WIN32
+            QChar pathSep = ';';
+#else
+            QChar pathSep = ':';
+#endif
+            name = QFileInfo(name).fileName();
+            QString path =
+                QProcessEnvironment::systemEnvironment().value("PATH");
+            DEBUG << "findInPath: seeking location for binary " << name
+                  << ": system path is " << path << endl;
+            if (installPath != "") {   
+                DEBUG << "findInPath: install path is " << installPath
+                      << ", adding to system path" << endl;
+                //!!! path = path + pathSep + installPath;
+                path = installPath + pathSep + path;
+            }
+#ifndef Q_OS_WIN32
+            //!!!
+            path = path + ":/usr/local/bin";
+            DEBUG << "... adding /usr/local/bin just in case (fix and add settings dlg please)"
+                    << endl;
+#endif
+            QStringList elements = path.split(pathSep, QString::SkipEmptyParts);
+            foreach (QString element, elements) {
+                QString full = QDir(element).filePath(name);
+                QFileInfo fi(full);
+                DEBUG << "findInPath: looking at " << full << endl;
+                if (fi.exists() && fi.isFile()) {
+                    DEBUG << "findInPath: it's a file" << endl;
+                    if (!executableRequired || fi.isExecutable()) {
+                        name = full;
+                        DEBUG << "findInPath: found at " << name << endl;
+                        found = true;
+                        break;
+                    }
+                }
+            }
+        } else {
+            // absolute path given
+            QFileInfo fi(name);
+            DEBUG << "findInPath: looking at absolute path " << name << endl;
+            if (fi.exists() && fi.isFile()) {
+                DEBUG << "findInPath: it's a file" << endl;
+                if (!executableRequired || fi.isExecutable()) {
+                    DEBUG << "findInPath: found at " << name << endl;
+                    found = true;
+                }
+            }
+        }
+    }
+#ifdef Q_OS_WIN32
+    if (!found) {
+        if (!name.endsWith(".exe")) {
+            return findInPath(name + ".exe", installPath, executableRequired);
+        }
+    }
+#endif
+    if (found) {
+        return name;
+    } else {
+        return "";
+    }
+}
+
+#ifdef Q_OS_WIN32
+QString getUserRealName()
+{
+    TCHAR buf[1024];
+    long unsigned int maxlen = 1000;
+    LPTSTR info = buf;
+
+    if (!GetUserNameEx(NameDisplay, info, &maxlen)) {
+        DEBUG << "GetUserNameEx failed: " << GetLastError() << endl;
+        return "";
+    }
+
+#ifdef UNICODE
+    return QString::fromUtf16((const unsigned short *)info);
+#else
+    return QString::fromLocal8Bit(info);
+#endif
+}
+#else
+#ifdef Q_OS_MAC
+// Nothing here: definition is in common_osx.mm
+#else
+QString getUserRealName()
+{
+    const int maxlen = 1023;
+    char buf[maxlen + 2];
+
+    if (getlogin_r(buf, maxlen)) return "";
+
+    struct passwd *p = getpwnam(buf);
+    if (!p) return "";
+    
+    QString s(p->pw_gecos);
+    if (s != "") s = s.split(',')[0];
+    return s;
+}
+#endif
+#endif
+
+void loseControllingTerminal()
+{
+#ifndef Q_OS_WIN32
+
+    if (!isatty(0)) {
+        DEBUG << "stdin is not a terminal" << endl;
+    } else {
+        DEBUG << "stdin is a terminal, detaching from it" << endl;
+        if (ioctl(0, TIOCNOTTY, NULL) < 0) {
+            perror("ioctl failed");
+            DEBUG << "ioctl for TIOCNOTTY on stdin failed (errno = " << errno << ")" << endl;
+        } else {
+            DEBUG << "ioctl for TIOCNOTTY on stdin succeeded" << endl;
+	    return;
+        }
+    }
+
+    int ttyfd = open("/dev/tty", O_RDWR);
+    if (ttyfd < 0) {
+        DEBUG << "failed to open controlling terminal" << endl;
+    } else {
+        if (ioctl(ttyfd, TIOCNOTTY, NULL) < 0) {
+            perror("ioctl failed");
+            DEBUG << "ioctl for TIOCNOTTY on controlling terminal failed (errno = " << errno << ")" << endl;
+        } else {
+            DEBUG << "ioctl for TIOCNOTTY on controlling terminal succeeded" << endl;
+	    return;
+        }
+    }
+
+#endif
+}
+
+void installSignalHandlers()
+{
+#ifndef Q_OS_WIN32
+    sigset_t sgnals;
+    sigemptyset (&sgnals);
+    sigaddset(&sgnals, SIGHUP);
+    sigaddset(&sgnals, SIGCONT);
+    pthread_sigmask(SIG_BLOCK, &sgnals, 0);
+#endif
+}
+
+ProcessStatus
+GetProcessStatus(int pid)
+{
+#ifdef _WIN32
+    HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+    if (!handle) {
+        return ProcessNotRunning;
+    } else {
+        CloseHandle(handle);
+        return ProcessRunning;
+    }
+#else
+    if (kill(getpid(), 0) == 0) {
+        if (kill(pid, 0) == 0) {
+            return ProcessRunning;
+        } else {
+            return ProcessNotRunning;
+        }
+    } else {
+        return UnknownProcessStatus;
+    }
+#endif
+}
+
+FolderStatus getFolderStatus(QString path)
+{
+    if (path != "/" && path.endsWith("/")) {
+        path = path.left(path.length()-1);
+    }
+    DEBUG << "getFolderStatus: " << path << endl;
+    QFileInfo fi(path);
+    if (fi.exists()) {
+        DEBUG << "exists" << endl;
+        QDir dir(path);
+        if (!dir.exists()) { // returns false for files
+            DEBUG << "not directory" << endl;
+            return FolderIsFile;
+        }
+        if (QDir(dir.filePath(".hg")).exists()) {
+            DEBUG << "has repo" << endl;
+            return FolderHasRepo;
+        }
+        return FolderExists;
+    } else {
+        QDir parent = fi.dir();
+        if (parent.exists()) {
+            DEBUG << "parent exists" << endl;
+            return FolderParentExists;
+        }
+        return FolderUnknown;
+    }
+}
+
+QString getContainingRepoFolder(QString path)
+{
+    if (getFolderStatus(path) == FolderHasRepo) return "";
+
+    QFileInfo me(path);
+    QFileInfo parent(me.dir().absolutePath());
+
+    while (me != parent) {
+        QString parentPath = parent.filePath();
+        if (getFolderStatus(parentPath) == FolderHasRepo) {
+            return parentPath;
+        }
+        me = parent;
+        parent = me.dir().absolutePath();
+    }
+
+    return "";
+}
+
+QString xmlEncode(QString s)
+{
+    s
+	.replace("&", "&amp;")
+	.replace("<", "&lt;")
+	.replace(">", "&gt;")
+	.replace("\"", "&quot;")
+	.replace("'", "&apos;");
+
+    return s;
+}
+
+QString uniDecode(QString s)
+{
+    QString d;
+    for (int i = 0; i < s.length(); ++i) {
+        // backslash escaped with another backslash: replace with a
+        // single backslash and skip on
+        if (i+1 < s.length() && s[i] == '\\' && s[i+1] == '\\') {
+            d += '\\';
+            i += 1;
+            continue;
+        }
+        // unescaped backslash followed by u and at least four more
+        // chars: replace with Unicode character
+        if (i+5 < s.length() && s[i] == '\\' && s[i+1] == 'u') {
+            QString uni = s.mid(i+2, 4);
+            QByteArray ba = QByteArray::fromHex(uni.toLatin1());
+            d += QChar(ba[1], ba[0]);
+            i += 5;
+            continue;
+        }
+        // default case: leave alone
+        d += s[i];
+    }
+    return d;
+}
+
+QByteArray randomKey()
+{
+    static int len = 16;
+
+    QByteArray ba;
+    ba.resize(len);
+    char *buffer = ba.data();
+
+#ifdef Q_OS_WIN32
+
+    HCRYPTPROV provider = 0;
+    if (!CryptAcquireContextW(&provider, 0, 0,
+                              PROV_RSA_FULL,
+                              CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
+        return QByteArray();
+    }
+
+    if (!CryptGenRandom(provider, len, (BYTE *)buffer)) {
+        CryptReleaseContext(provider, 0);
+        return QByteArray();
+    }
+
+    CryptReleaseContext(provider, 0);
+
+#else
+    
+    FILE *rf = fopen("/dev/urandom", "r");
+    if (!rf) {
+        return QByteArray();
+    }
+
+    for (int i = 0; i < len; ++i) {
+        buffer[i] = fgetc(rf);
+    }
+
+    fclose(rf);
+
+#endif
+
+    return ba;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/common.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,76 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 COMMON_H
+#define COMMON_H
+
+#include <QString>
+
+extern QString findInPath(QString name, QString installPath, bool executable);
+
+extern QString getUserRealName();
+
+extern void loseControllingTerminal();
+
+void installSignalHandlers();
+
+/**
+ * Status used in testing whether a folder argument (received from the
+ * user) is valid for particular uses.
+ */
+enum FolderStatus {
+    FolderUnknown,      /// Neither the folder nor its parent exists
+    FolderParentExists, /// The folder is absent, but its parent exists
+    FolderExists,       /// The folder exists and has no .hg repo in it
+    FolderHasRepo,      /// The folder exists and has an .hg repo in it
+    FolderIsFile        /// The "folder" is actually a file
+};
+
+FolderStatus getFolderStatus(QString path);
+
+enum ProcessStatus {
+    ProcessRunning,
+    ProcessNotRunning,
+    UnknownProcessStatus
+};
+
+ProcessStatus GetProcessStatus(int pid);
+
+/**
+ * If the given path is somewhere within an existing repository,
+ * return the path of the root directory of the repository (i.e. the
+ * one with .hg in it).
+ *
+ * If the given path is _not_ in a repository, or the given path _is_
+ * the root directory of a repository, return QString().  Use
+ * getFolderStatus to distinguish between these cases.
+ */
+QString getContainingRepoFolder(QString path);
+
+QString xmlEncode(QString);
+
+QString uniDecode(QString);
+
+/**
+ * Generate a 16-byte random key using urandom or equivalent
+ */
+QByteArray randomKey();
+    
+
+#endif 	//COMMON_H
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/common_osx.mm	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,16 @@
+
+#include "common.h"
+
+#include <QString>
+
+#ifdef Q_OS_MAC
+
+#include <Foundation/Foundation.h>
+
+QString getUserRealName()
+{
+    NSString *s = NSFullUserName();
+    return QString::fromLocal8Bit([s UTF8String]);
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/confirmcommentdialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,275 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 "confirmcommentdialog.h"
+#include "common.h"
+#include "debug.h"
+
+#include <QMessageBox>
+#include <QInputDialog>
+#include <QGridLayout>
+#include <QLabel>
+#include <QTextEdit>
+#include <QDialogButtonBox>
+
+ConfirmCommentDialog::ConfirmCommentDialog(QWidget *parent,
+                                           QString title,
+                                           QString introText,
+                                           QString initialComment,
+                                           QString okButtonText) :
+    QDialog(parent)
+{
+    setWindowTitle(title);
+
+    QGridLayout *layout = new QGridLayout;
+    setLayout(layout);
+    QLabel *label = new QLabel(introText);
+    label->setWordWrap(true);
+    layout->addWidget(label, 0, 0);
+
+    m_textEdit = new QTextEdit;
+    m_textEdit->setAcceptRichText(false);
+    m_textEdit->document()->setPlainText(initialComment);
+    m_textEdit->setMinimumWidth(360);
+    connect(m_textEdit, SIGNAL(textChanged()), this, SLOT(commentChanged()));
+    layout->addWidget(m_textEdit, 1, 0);
+
+    QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok |
+                                                  QDialogButtonBox::Cancel);
+    layout->addWidget(bbox, 2, 0);
+    m_ok = bbox->button(QDialogButtonBox::Ok);
+    m_ok->setDefault(true);
+    m_ok->setEnabled(initialComment != "");
+    m_ok->setText(okButtonText);
+    bbox->button(QDialogButtonBox::Cancel)->setAutoDefault(false);
+
+    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
+    connect(bbox, SIGNAL(rejected()), this, SLOT(reject()));
+}
+
+void ConfirmCommentDialog::commentChanged()
+{
+    m_ok->setEnabled(getComment() != "");
+}
+
+QString ConfirmCommentDialog::getComment() const
+{
+/*
+    DEBUG << "ConfirmCommentDialog: as html is:" << endl;
+    DEBUG << m_textEdit->document()->toHtml() << endl;
+    DEBUG << "ConfirmCommentDialog: as plain text is:" << endl;
+*/
+    QString t = m_textEdit->document()->toPlainText();
+/*
+    DEBUG << t << endl;
+    DEBUG << "Characters: " << endl;
+    for (int i = 0; i < t.length(); ++i) DEBUG << t[i].unicode();
+    DEBUG << endl;
+*/
+    return t;
+}
+
+QString ConfirmCommentDialog::buildFilesText(QString intro, QStringList files)
+{
+    QString text;
+
+    if (intro == "") text = "<qt>";
+    else text = "<qt>" + intro + "<p>";
+
+    text += "<code>";
+    if (files.empty()) {
+        text += "&nbsp;&nbsp;&nbsp;</code>";
+        text += tr("(no files: metadata only)");
+    } else {
+        foreach (QString file, files) {
+            text += "&nbsp;&nbsp;&nbsp;" + xmlEncode(file) + "<br>";
+        }
+        text += "</code></qt>";
+    }
+
+    return text;
+}
+
+bool ConfirmCommentDialog::confirm(QWidget *parent,
+                                   QString title,
+                                   QString head,
+                                   QString text,
+                                   QString okButtonText)
+{
+    QMessageBox box(QMessageBox::Question,
+                    title,
+                    head,
+                    QMessageBox::Cancel,
+                    parent);
+
+    box.setInformativeText(text);
+
+    QPushButton *ok = box.addButton(QMessageBox::Ok);
+    ok->setText(okButtonText);
+    box.setDefaultButton(QMessageBox::Ok);
+    if (box.exec() == -1) return false;
+    return box.standardButton(box.clickedButton()) == QMessageBox::Ok;
+}
+
+bool ConfirmCommentDialog::confirmDangerous(QWidget *parent,
+                                            QString title,
+                                            QString head,
+                                            QString text,
+                                            QString okButtonText)
+{
+    QMessageBox box(QMessageBox::Warning,
+                    title,
+                    head,
+                    QMessageBox::Cancel,
+                    parent);
+
+    box.setInformativeText(text);
+
+    QPushButton *ok = box.addButton(QMessageBox::Ok);
+    ok->setText(okButtonText);
+    box.setDefaultButton(QMessageBox::Cancel);
+    if (box.exec() == -1) return false;
+    return box.standardButton(box.clickedButton()) == QMessageBox::Ok;
+}
+
+bool ConfirmCommentDialog::confirmFilesAction(QWidget *parent,
+                                              QString title,
+                                              QString introText,
+                                              QString introTextWithCount,
+                                              QStringList files,
+                                              QString okButtonText)
+{
+    QString text;
+    if (files.size() <= 10) {
+        text = buildFilesText(introText, files);
+    } else {
+        text = "<qt>" + introTextWithCount + "</qt>";
+    }
+    return confirm(parent, title, text, "", okButtonText);
+}
+
+bool ConfirmCommentDialog::confirmDangerousFilesAction(QWidget *parent,
+                                                       QString title,
+                                                       QString introText,
+                                                       QString introTextWithCount,
+                                                       QStringList files,
+                                                       QString okButtonText)
+{
+    QString text;
+    if (files.size() <= 10) {
+        text = buildFilesText(introText, files);
+    } else {
+        text = "<qt>" + introTextWithCount + "</qt>";
+    }
+    return confirmDangerous(parent, title, text, "", okButtonText);
+}
+
+bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent,
+                                                     QString title,
+                                                     QString introText,
+                                                     QString introTextWithCount,
+                                                     QStringList files,
+                                                     QString &comment,
+                                                     QString okButtonText)
+{
+    return confirmAndComment(parent, title, introText,
+                             introTextWithCount, files, comment, false,
+                             okButtonText);
+}
+
+bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent,
+                                                    QString title,
+                                                    QString introText,
+                                                    QString introTextWithCount,
+                                                    QStringList files,
+                                                    QString &comment,
+                                                    QString okButtonText)
+{
+    return confirmAndComment(parent, title, introText,
+                             introTextWithCount, files, comment, true,
+                             okButtonText);
+}
+
+bool ConfirmCommentDialog::confirmAndComment(QWidget *parent,
+                                             QString title,
+                                             QString introText,
+                                             QString introTextWithCount,
+                                             QStringList files,
+                                             QString &comment,
+                                             bool longComment, 
+                                             QString okButtonText)
+{
+    QString text;
+    if (files.size() <= 10) {
+        text = buildFilesText(introText, files);
+    } else {
+        text = "<qt>" + introTextWithCount;
+    }
+    text += tr("<p>Please enter your comment:</qt>");
+    return confirmAndComment(parent, title, text, comment, longComment,
+                             okButtonText);
+}
+
+bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent,
+                                                     QString title,
+                                                     QString introText,
+                                                     QString &comment,
+                                                     QString okButtonText)
+{
+    return confirmAndComment(parent, title, introText, comment, false,
+                             okButtonText);
+}
+
+bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent,
+                                                    QString title,
+                                                    QString introText,
+                                                    QString &comment,
+                                                    QString okButtonText)
+{
+    return confirmAndComment(parent, title, introText, comment, true,
+                             okButtonText);
+}
+
+bool ConfirmCommentDialog::confirmAndComment(QWidget *parent,
+                                             QString title,
+                                             QString introText,
+                                             QString &comment,
+                                             bool longComment,
+                                             QString okButtonText)
+{
+    bool ok = false;
+    if (!longComment) {
+        QInputDialog d(parent);
+        d.setWindowTitle(title);
+        d.setLabelText(introText);
+        d.setTextValue(comment);
+        d.setOkButtonText(okButtonText);
+        d.setTextEchoMode(QLineEdit::Normal);
+        if (d.exec() == QDialog::Accepted) {
+            comment = d.textValue();
+            ok = true;
+        }
+    } else {
+        ConfirmCommentDialog d(parent, title, introText, comment, okButtonText);
+        if (d.exec() == QDialog::Accepted) {
+            comment = d.getComment();
+            ok = true;
+        }
+    }
+
+    return ok;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/confirmcommentdialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,121 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 CONFIRMCOMMENTDIALOG_H
+#define CONFIRMCOMMENTDIALOG_H
+
+#include <QDialog>
+#include <QWidget>
+#include <QString>
+#include <QStringList>
+#include <QTextEdit>
+#include <QPushButton>
+
+class ConfirmCommentDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    static bool confirm(QWidget *parent,
+                        QString title,
+                        QString head,
+                        QString text,
+                        QString okButtonText);
+    
+    static bool confirmDangerous(QWidget *parent,
+                                 QString title,
+                                 QString head,
+                                 QString text,
+                                 QString okButtonText);
+    
+    static bool confirmFilesAction(QWidget *parent,
+                                   QString title,
+                                   QString introText,
+                                   QString introTextWithCount,
+                                   QStringList files,
+                                   QString okButtonText);
+
+    static bool confirmDangerousFilesAction(QWidget *parent,
+                                            QString title,
+                                            QString introText,
+                                            QString introTextWithCount,
+                                            QStringList files,
+                                            QString okButtonText);
+
+    static bool confirmAndGetShortComment(QWidget *parent,
+                                          QString title,
+                                          QString introText,
+                                          QString introTextWithCount,
+                                          QStringList files,
+                                          QString &comment,
+                                          QString okButtonText);
+
+    static bool confirmAndGetLongComment(QWidget *parent,
+                                         QString title,
+                                         QString introText,
+                                         QString introTextWithCount,
+                                         QStringList files,
+                                         QString &comment,
+                                         QString okButtonText);
+
+    static bool confirmAndGetShortComment(QWidget *parent,
+                                          QString title,
+                                          QString introText,
+                                          QString &comment,
+                                          QString okButtonText);
+
+    static bool confirmAndGetLongComment(QWidget *parent,
+                                         QString title,
+                                         QString introText,
+                                         QString &comment,
+                                         QString okButtonText);
+
+private slots:
+    void commentChanged();
+
+private:
+    ConfirmCommentDialog(QWidget *parent,
+                         QString title,
+                         QString introText,
+                         QString initialComment,
+                         QString okButtonText);
+
+    static bool confirmAndComment(QWidget *parent,
+                                  QString title,
+                                  QString introText,
+                                  QString introTextWithCount,
+                                  QStringList files,
+                                  QString &comment,
+                                  bool longComment,
+                                  QString okButtonText);
+
+    static bool confirmAndComment(QWidget *parent,
+                                  QString title,
+                                  QString introText,
+                                  QString &comment,
+                                  bool longComment,
+                                  QString okButtonText);
+
+    static QString buildFilesText(QString intro, QStringList files);
+
+    QString getComment() const;
+
+    QTextEdit *m_textEdit;
+    QPushButton *m_ok;
+};
+
+#endif // CONFIRMCOMMENTDIALOG_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/connectionitem.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,186 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "connectionitem.h"
+#include "uncommitteditem.h"
+
+#include "changesetitem.h"
+#include "changeset.h"
+#include "colourset.h"
+#include "textabbrev.h"
+
+#include <QPainter>
+#include <QFont>
+
+QRectF
+ConnectionItem::boundingRect() const
+{
+    if (!(m_child || m_uncommitted)) return QRectF();
+    float xscale = 100;
+    float yscale = 90;
+    float size = 50;
+
+    int c_col, c_row;
+    if (m_child) {
+        c_col = m_child->column(); c_row = m_child->row();
+    } else {
+        c_col = m_uncommitted->column(); c_row = m_uncommitted->row();
+    }
+
+    int p_col, p_row;
+    if (m_parent) {
+        p_col = m_parent->column(); p_row = m_parent->row();
+    } else {
+        p_col = c_col - 1; p_row = c_row + 1;
+    }
+
+    return QRectF(xscale * c_col + size/2 - 2,
+		  yscale * c_row + size - 22,
+		  xscale * p_col - xscale * c_col + 6,
+		  yscale * p_row - yscale * c_row - size + 44)
+	.normalized();
+}
+
+void
+ConnectionItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
+{
+    if (!(m_child || m_uncommitted)) return;
+    QPainterPath p;
+
+    paint->save();
+
+    int alpha = 255;
+    if (m_child && m_child->isClosed()) alpha = 90;
+
+    ColourSet *colourSet = ColourSet::instance();
+    QString branch;
+    if (m_child) branch = m_child->getChangeset()->branch();
+    else branch = m_uncommitted->branch();
+    QColor branchColour = colourSet->getColourFor(branch);
+
+    branchColour.setAlpha(alpha);
+
+    Qt::PenStyle ls = Qt::SolidLine;
+    if (!m_child) ls = Qt::DashLine;
+
+    QTransform t = paint->worldTransform();
+    float scale = std::min(t.m11(), t.m22());
+    if (scale < 0.2) {
+	paint->setPen(QPen(branchColour, 0, ls));
+    } else {
+	paint->setPen(QPen(branchColour, 2, ls));
+    }
+
+    float xscale = 100;
+
+    float yscale = 90;
+    float size = 50;
+    float ygap = yscale - size - 2;
+
+    int c_col, c_row;
+    if (m_child) {
+        c_col = m_child->column(); c_row = m_child->row();
+    } else {
+        c_col = m_uncommitted->column(); c_row = m_uncommitted->row();
+    }
+
+    int p_col, p_row;
+    if (m_parent) {
+        p_col = m_parent->column(); p_row = m_parent->row();
+    } else {
+        p_col = c_col - 1; p_row = c_row + 1;
+    }
+
+    float c_x = xscale * c_col + size/2;
+    float p_x = xscale * p_col + size/2;
+
+    // ensure line reaches the box, even if it's in a small height --
+    // doesn't matter if we overshoot as the box is opaque and has a
+    // greater Z value
+    p.moveTo(c_x, yscale * c_row + size - 20);
+
+    p.lineTo(c_x, yscale * c_row + size);
+
+    if (p_col == c_col) {
+
+	p.lineTo(p_x, yscale * p_row);
+
+    } else if (m_type == Split || m_type == Normal) {
+
+	// place the bulk of the line on the child (i.e. branch) row
+
+	if (abs(p_row - c_row) > 1) {
+	    p.lineTo(c_x, yscale * p_row - ygap);
+	}
+
+	p.cubicTo(c_x, yscale * p_row,
+		  p_x, yscale * p_row - ygap,
+		  p_x, yscale * p_row);
+
+    } else if (m_type == Merge) {
+
+	// place bulk of the line on the parent row
+
+	p.cubicTo(c_x, yscale * c_row + size + ygap,
+		  p_x, yscale * c_row + size,
+		  p_x, yscale * c_row + size + ygap);
+
+	if (abs(p_row - c_row) > 1) {
+	    p.lineTo(p_x, yscale * p_row);
+	}
+    }
+
+    if (m_parent) {
+
+        // ensure line reaches the node -- again doesn't matter if we
+        // overshoot
+        p.lineTo(p_x, yscale * p_row + 20);
+
+    } else {
+
+        // no parent: merge from closed branch: draw only half the line
+        paint->setClipRect(QRectF((c_x + p_x)/2, yscale * c_row + size - 22,
+                                  xscale, yscale));
+    }
+    
+    paint->drawPath(p);
+
+    if (!m_parent) {
+
+        // merge from closed branch: draw branch name
+
+        paint->setClipping(false);
+
+        QFont f;
+        f.setPixelSize(11);
+        f.setBold(true);
+        f.setItalic(false);
+	paint->setFont(f);
+
+	QString branch = m_mergedBranch;
+        if (branch == "") branch = "default";
+	int wid = xscale;
+	branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid);
+	paint->drawText((c_x + p_x)/2 - wid - 2,
+                        yscale * c_row + size + ygap/2 + 2,
+                        branch);
+    }
+    
+    paint->restore();
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/connectionitem.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,63 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 CONNECTIONITEM_H
+#define CONNECTIONITEM_H
+
+#include <QGraphicsItem>
+
+class Connection;
+
+class ChangesetItem;
+class UncommittedItem;
+
+class ConnectionItem : public QGraphicsItem
+{
+public:
+    enum Type {
+	Normal,
+	Split,
+	Merge
+    };
+
+    ConnectionItem() : m_type(Normal), m_parent(0), m_child(0) { }
+
+    virtual QRectF boundingRect() const;
+    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
+
+    Type connectionType() const { return m_type; }
+    void setConnectionType(Type t) { m_type = t; }
+
+    //!!! 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; }
+    void setChild(UncommittedItem *u) { m_uncommitted = u; }
+    void setMergedBranch(QString mb) { m_mergedBranch = mb; }
+
+private:
+    Type m_type;
+    ChangesetItem *m_parent;
+    ChangesetItem *m_child;
+    UncommittedItem *m_uncommitted;
+    QString m_mergedBranch;
+};
+
+#endif // CONNECTIONITEM_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/debug.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,93 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "debug.h"
+
+#include <QString>
+#include <QUrl>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QFile>
+#include <QDir>
+#include <QCoreApplication>
+#include <QDateTime>
+#include <QStandardPaths>
+
+#include <cstdio>
+
+QDebug &
+getEasyHgDebug()
+{
+    static QFile *logFile = 0;
+    static QDebug *debug = 0;
+    static QMutex mutex;
+    static char *prefix;
+    mutex.lock();
+    if (!debug) {
+        prefix = new char[20];
+        sprintf(prefix, "[%lu]", (unsigned long)QCoreApplication::applicationPid());
+        QString logFileName = QDir::homePath() + "/.easyhg.log"; // the fallback
+        QString logDir = QStandardPaths::writableLocation
+            (QStandardPaths::DataLocation);
+        if (logDir != "" &&
+            (QDir(logDir).exists() ||
+             QDir().mkpath(logDir))) {
+            logFileName = logDir + "/debug.log";
+        }
+        logFile = new QFile(logFileName);
+        if (logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+            QDebug(QtDebugMsg) << (const char *)prefix
+                               << "Opened debug log file "
+                               << logFile->fileName();
+            debug = new QDebug(logFile);
+        } else {
+            QDebug(QtWarningMsg) << (const char *)prefix
+                                 << "Failed to open debug log file "
+                                 << logFile->fileName()
+                                 << " for writing, using console debug instead";
+            debug = new QDebug(QtDebugMsg);
+            delete logFile;
+            logFile = 0;
+        }
+        *debug << endl << (const char *)prefix << "Log started at "
+               << QDateTime::currentDateTime().toString();
+    }
+    mutex.unlock();
+
+    QDebug &dref = *debug;
+    return dref << endl << (const char *)prefix;
+}
+
+QDebug &
+operator<<(QDebug &dbg, const std::string &s)
+{
+    dbg << QString::fromUtf8(s.c_str());
+    return dbg;
+}
+
+std::ostream &
+operator<<(std::ostream &target, const QString &str)
+{
+    return target << str.toLocal8Bit().data();
+}
+
+std::ostream &
+operator<<(std::ostream &target, const QUrl &u)
+{
+    return target << "<" << u.toString() << ">";
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/debug.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,67 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _DEBUG_H_
+#define _DEBUG_H_
+
+#include <QDebug>
+#include <QTextStream>
+#include <string>
+#include <iostream>
+
+class QString;
+class QUrl;
+
+QDebug &operator<<(QDebug &, const std::string &);
+std::ostream &operator<<(std::ostream &, const QString &);
+std::ostream &operator<<(std::ostream &, const QUrl &);
+
+#ifndef NDEBUG
+
+extern QDebug &getEasyHgDebug();
+
+#define DEBUG getEasyHgDebug()
+
+template <typename T>
+inline QDebug &operator<<(QDebug &d, const T &t) {
+    QString s;
+    QTextStream ts(&s);
+    ts << t;
+    d << s;
+    return d;
+}
+
+#else
+
+class NoDebug
+{
+public:
+    inline NoDebug() {}
+    inline ~NoDebug(){}
+
+    template <typename T>
+    inline NoDebug &operator<<(const T &) { return *this; }
+
+    inline NoDebug &operator<<(QTextStreamFunction) { return *this; }
+};
+
+#define DEBUG NoDebug()
+
+#endif /* !NDEBUG */
+
+#endif /* !_DEBUG_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/filestates.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,239 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 "filestates.h"
+
+#include "debug.h"
+
+#include <QMap>
+
+FileStates::FileStates()
+{
+}
+
+void FileStates::clearBuckets()
+{
+    m_clean.clear();
+    m_modified.clear();
+    m_added.clear();
+    m_removed.clear();
+    m_missing.clear();
+    m_inConflict.clear();
+    m_unknown.clear();
+    m_ignored.clear();
+}
+
+FileStates::State FileStates::charToState(QChar c, bool *ok)
+{
+    // Note that InConflict does not correspond to a stat char -- it's
+    // reported separately, by resolve --list, which shows U for
+    // Unresolved -- stat reports files in conflict as M, which means
+    // they will appear in more than one bin if we handle them
+    // naively.  'u' is also used by stat as the command option for
+    // Unknown, but the stat output uses ? for these so there's no
+    // ambiguity in parsing.
+
+    if (ok) *ok = true;
+    if (c == 'M') return Modified;
+    if (c == 'A') return Added;
+    if (c == 'R') return Removed;
+    if (c == '!') return Missing;
+    if (c == 'U') return InConflict;
+    if (c == '?') return Unknown;
+    if (c == 'C') return Clean;
+    if (c == 'I') return Ignored;
+    if (ok) *ok = false;
+    return Unknown;
+}
+
+QStringList *FileStates::stateToBucket(State s)
+{
+    switch (s) {
+    case Clean: return &m_clean;
+    case Modified: return &m_modified;
+    case Added: return &m_added;
+    case Unknown: return &m_unknown;
+    case Removed: return &m_removed;
+    case Missing: return &m_missing;
+    case InConflict: return &m_inConflict;
+    case Ignored: return &m_ignored;
+
+    default: return &m_clean;
+    }
+}
+
+void FileStates::parseStates(QString text)
+{
+    text.replace("\r\n", "\n");
+
+    clearBuckets();
+    m_stateMap.clear();
+
+    QStringList lines = text.split("\n", QString::SkipEmptyParts);
+
+    foreach (QString line, lines) {
+
+        if (line.length() < 3 || line[1] != ' ') {
+            continue;
+        }
+
+        QChar c = line[0];
+        bool ok = false;
+        State s = charToState(c, &ok);
+        if (!ok) continue;
+
+        QString file = line.right(line.length() - 2);
+        m_stateMap[file] = s;
+    }
+
+    foreach (QString file, m_stateMap.keys()) {
+        QStringList *bucket = stateToBucket(m_stateMap[file]);
+        if (bucket) bucket->push_back(file);
+    }
+
+    DEBUG << "FileStates: "
+          << m_modified.size() << " modified, " << m_added.size()
+          << " added, " << m_removed.size() << " removed, " << m_missing.size()
+          << " missing, " << m_inConflict.size() << " in conflict, "
+          << m_unknown.size() << " unknown" << endl;
+}
+
+QStringList FileStates::filesInState(State s) const
+{
+    QStringList *sl = const_cast<FileStates *>(this)->stateToBucket(s);
+    if (sl) return *sl;
+    else return QStringList();
+}
+
+bool FileStates::isInState(QString file, State s) const
+{
+    return filesInState(s).contains(file);
+}
+
+FileStates::State FileStates::stateOf(QString file) const
+{
+    if (m_stateMap.contains(file)) {
+        return m_stateMap[file];
+    }
+    DEBUG << "FileStates: WARNING: getStateOfFile: file "
+            << file << " is unknown to us: returning Unknown state, "
+            << "but unknown to us is not supposed to be the same "
+            << "thing as unknown state..."
+            << endl;
+    return Unknown;
+}
+
+bool FileStates::isKnown(QString file) const
+{
+    return (m_stateMap.contains(file));
+}
+
+QStringList FileStates::trackedFiles() const
+{
+    QStringList all;
+    all << filesInState(Modified);
+    all << filesInState(Added);
+    all << filesInState(Removed);
+    all << filesInState(InConflict);
+    all << filesInState(Missing);
+    all << filesInState(Clean);
+    return all;
+}
+
+FileStates::Activities FileStates::activitiesSupportedBy(State s)
+{
+    Activities a;
+
+    switch (s) {
+
+    case Modified:
+        a << Annotate << Diff << Commit << Revert << Rename << Copy << Remove << ShowIn;
+        break;
+
+    case Added:
+        a << Commit << Revert << Rename << Copy << Remove << ShowIn;
+        break;
+        
+    case Removed:
+        a << Commit << Revert << Add;
+        break;
+
+    case InConflict:
+        a << Annotate << Diff << RedoMerge << MarkResolved << Revert << ShowIn;
+        break;
+
+    case Missing:
+        a << Revert << Remove;
+        break;
+        
+    case Unknown:
+        a << Add << Ignore << ShowIn;
+        break;
+
+    case Clean:
+        a << Annotate << Rename << Copy << Remove << ShowIn;
+        break;
+
+    case Ignored:
+        a << UnIgnore << ShowIn;
+        break;
+    }
+
+    return a;
+}
+
+bool FileStates::supportsActivity(State s, Activity a)
+{
+    return activitiesSupportedBy(s).contains(a);
+}
+
+int FileStates::activityGroup(Activity a)
+{
+    switch (a) {
+    case Annotate: case Diff: return 0;
+    case Commit: case Revert: return 1;
+    case Rename: case Copy: return 2;
+    case Add: case Remove: return 3;
+    case RedoMerge: case MarkResolved: return 4;
+    case Ignore: case UnIgnore: return 5;
+    case ShowIn: return 6;
+    }
+    return 0;
+}
+
+bool FileStates::supportsActivity(QString file, Activity a) const
+{
+    return supportsActivity(stateOf(file), a);
+}
+
+QStringList FileStates::filesSupportingActivity(Activity a) const
+{
+    QStringList f;
+    for (int i = int(FirstState); i <= int(LastState); ++i) {
+        State s = (State)i;
+        if (supportsActivity(s, a)) {
+            f << filesInState(s);
+        }
+    }
+    return f;
+}
+
+FileStates::Activities FileStates::activitiesSupportedBy(QString file) const
+{
+    return activitiesSupportedBy(stateOf(file));
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/filestates.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,113 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 FILESTATES_H
+#define FILESTATES_H
+
+#include <QStringList>
+#include <QMap>
+#include <QString>
+
+class FileStates
+{
+public:
+    FileStates();
+
+    enum State {
+
+        // These are in the order in which they want to be listed in
+        // the interface
+
+        Modified,
+        Added,
+        Removed,
+        InConflict,
+        Missing,
+        Unknown,
+        Clean,
+        Ignored,
+
+        FirstState = Modified,
+        LastState = Ignored
+    };
+
+    void parseStates(QString text);
+
+    bool isInState(QString file, State s) const;
+    QStringList filesInState(State s) const;
+    State stateOf(QString file) const;
+    bool isKnown(QString file) const;
+
+    QStringList trackedFiles() const;
+
+    enum Activity {
+
+        // These are in the order in which they want to be listed in
+        // the context menu
+
+        Diff,
+        Annotate,
+
+        Commit,
+        Revert,
+
+        Rename,
+        Copy,
+
+        Add,
+        Remove,
+
+        RedoMerge,
+        MarkResolved,
+
+        Ignore,
+        UnIgnore,
+
+        ShowIn,
+
+        FirstActivity = Diff,
+        LastActivity = UnIgnore
+    };
+
+    typedef QList<Activity> Activities;
+
+    static bool supportsActivity(State s, Activity a);
+    static Activities activitiesSupportedBy(State s);
+    static int activityGroup(Activity a);
+    
+    bool supportsActivity(QString file, Activity a) const;
+    QStringList filesSupportingActivity(Activity) const;
+    Activities activitiesSupportedBy(QString file) const;
+
+private:
+    QStringList m_modified;
+    QStringList m_added;
+    QStringList m_unknown;
+    QStringList m_removed;
+    QStringList m_missing;
+    QStringList m_inConflict;
+    QStringList m_clean;
+    QStringList m_ignored;
+    QMap<QString, State> m_stateMap;
+
+    void clearBuckets();
+
+    State charToState(QChar, bool * = 0);
+    QStringList *stateToBucket(State);
+};
+
+#endif // FILESTATES_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/filestatuswidget.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,637 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "filestatuswidget.h"
+#include "debug.h"
+#include "multichoicedialog.h"
+#include "findwidget.h"
+
+#include <QLabel>
+#include <QListWidget>
+#include <QGridLayout>
+#include <QFileInfo>
+#include <QApplication>
+#include <QDateTime>
+#include <QPushButton>
+#include <QToolButton>
+#include <QDir>
+#include <QProcess>
+#include <QCheckBox>
+#include <QSettings>
+#include <QAction>
+
+FileStatusWidget::FileStatusWidget(QWidget *parent) :
+    QWidget(parent),
+    m_dateReference(0)
+{
+    QGridLayout *layout = new QGridLayout;
+    layout->setMargin(10);
+    setLayout(layout);
+
+    int row = 0;
+
+    m_noModificationsLabel = new QLabel;
+    setNoModificationsLabelText();
+    layout->addWidget(m_noModificationsLabel, row, 0);
+    m_noModificationsLabel->hide();
+
+    m_simpleLabels[FileStates::Clean] = tr("Unmodified:");
+    m_simpleLabels[FileStates::Modified] = tr("Modified:");
+    m_simpleLabels[FileStates::Added] = tr("Added:");
+    m_simpleLabels[FileStates::Removed] = tr("Removed:");
+    m_simpleLabels[FileStates::Missing] = tr("Missing:");
+    m_simpleLabels[FileStates::InConflict] = tr("In Conflict:");
+    m_simpleLabels[FileStates::Unknown] = tr("Untracked:");
+    m_simpleLabels[FileStates::Ignored] = tr("Ignored:");
+
+    m_actionLabels[FileStates::Annotate] = tr("Show annotated version");
+    m_actionLabels[FileStates::Diff] = tr("Diff to parent");
+    m_actionLabels[FileStates::Commit] = tr("Commit...");
+    m_actionLabels[FileStates::Revert] = tr("Revert to last committed state");
+    m_actionLabels[FileStates::Rename] = tr("Rename...");
+    m_actionLabels[FileStates::Copy] = tr("Copy...");
+    m_actionLabels[FileStates::Add] = tr("Add to version control");
+    m_actionLabels[FileStates::Remove] = tr("Remove from version control");
+    m_actionLabels[FileStates::RedoMerge] = tr("Redo merge");
+    m_actionLabels[FileStates::MarkResolved] = tr("Mark conflict as resolved");
+    m_actionLabels[FileStates::Ignore] = tr("Ignore...");
+    // Unignore is too difficult in fact, so we just offer to edit the hgignore
+    m_actionLabels[FileStates::UnIgnore] = tr("Edit .hgignore File");
+
+    // No "show in" under Unix at the moment.
+#if defined Q_OS_MAC
+    m_actionLabels[FileStates::ShowIn] = tr("Show in Finder");
+#elif defined Q_OS_WIN32
+    m_actionLabels[FileStates::ShowIn] = tr("Show in Windows Explorer");
+#endif
+
+    m_shortcuts[FileStates::ShowIn] = tr("Ctrl+Shift+S");
+    m_shortcuts[FileStates::Diff] = tr("Ctrl+Shift+D");
+    m_shortcuts[FileStates::Commit] = tr("Ctrl+Shift+C");
+    m_shortcuts[FileStates::Revert] = tr("Ctrl+Shift+R");
+
+    m_descriptions[FileStates::Clean] = tr("You have not changed these files.");
+    m_descriptions[FileStates::Modified] = tr("You have changed these files since you last committed them.");
+    m_descriptions[FileStates::Added] = tr("These files will be added to version control next time you commit them.");
+    m_descriptions[FileStates::Removed] = tr("These files will be removed from version control next time you commit them.<br>"
+                                             "They will not be deleted from the local folder.");
+    m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.<br>"
+                                             "If you intended to delete them, select them and use Remove to tell the version control system about it.<br>"
+                                             "If you deleted them by accident, right-click them and use Revert to restore their previous contents.");
+    m_descriptions[FileStates::InConflict] = tr("These files are unresolved following an incomplete merge.<br>Use Merge to try to resolve the merge again.");
+    m_descriptions[FileStates::Unknown] = tr("These files are in your working folder but are not under version control.<br>"
+//                                             "Select a file and use Add to place it under version control or Ignore to remove it from this list.");
+                                             "Select a file and use Add to place it under version control.");
+    m_descriptions[FileStates::Ignored] = tr("These files have names that match entries in the working folder's .hgignore file,<br>"
+                                             "and so will be ignored by the version control system.");
+
+    m_highlightExplanation = tr("Files highlighted <font color=#d40000>in red</font> "
+                                "have appeared since your most recent commit or update.");
+
+    m_boxesParent = new QWidget(this);
+    layout->addWidget(m_boxesParent, ++row, 0);
+
+    QGridLayout *boxesLayout = new QGridLayout;
+    boxesLayout->setMargin(0);
+    m_boxesParent->setLayout(boxesLayout);
+    int boxRow = 0;
+
+    for (int i = int(FileStates::FirstState);
+         i <= int(FileStates::LastState); ++i) {
+
+        FileStates::State s = FileStates::State(i);
+
+        QWidget *box = new QWidget(m_boxesParent);
+        QGridLayout *boxlayout = new QGridLayout;
+        boxlayout->setMargin(0);
+        box->setLayout(boxlayout);
+
+        boxlayout->addItem(new QSpacerItem(3, 3), 0, 0);
+
+        QLabel *label = new QLabel(labelFor(s));
+        label->setWordWrap(true);
+        boxlayout->addWidget(label, 1, 0);
+
+        QListWidget *w = new QListWidget;
+        m_stateListMap[s] = w;
+        w->setSelectionMode(QListWidget::ExtendedSelection);
+        boxlayout->addWidget(w, 2, 0);
+
+        connect(w, SIGNAL(itemSelectionChanged()),
+                this, SLOT(itemSelectionChanged()));
+        connect(w, SIGNAL(itemDoubleClicked(QListWidgetItem *)),
+                this, SLOT(itemDoubleClicked(QListWidgetItem *)));
+
+        FileStates::Activities activities = m_fileStates.activitiesSupportedBy(s);
+        int prevGroup = -1;
+        foreach (FileStates::Activity a, activities) {
+            QString label = m_actionLabels[a];
+            if (label.length() == 0) {
+                // Skip empty labels.
+                continue;
+            }
+            int group = FileStates::activityGroup(a);
+            if (group != prevGroup && prevGroup != -1) {
+                QAction *sep = new QAction("", w);
+                sep->setSeparator(true);
+                w->insertAction(0, sep);
+            }
+            prevGroup = group;
+            QAction *act = new QAction(label, w);
+            act->setProperty("state", s);
+            act->setProperty("activity", a);
+            if (m_shortcuts.contains(a)) {
+                QString shortcut = m_shortcuts[a];
+                act->setShortcut(shortcut);
+                act->setShortcutContext(Qt::WidgetShortcut);
+            }
+            connect(act, SIGNAL(triggered()), this, SLOT(menuActionActivated()));
+            w->insertAction(0, act);
+        }
+        w->setContextMenuPolicy(Qt::ActionsContextMenu);
+
+        boxlayout->addItem(new QSpacerItem(2, 2), 3, 0);
+
+        boxesLayout->addWidget(box, ++boxRow, 0);
+        m_boxes.push_back(box);
+        box->hide();
+    }
+
+    m_gridlyLayout = false;
+
+    layout->setRowStretch(++row, 20);
+
+    layout->addItem(new QSpacerItem(8, 8), ++row, 0);
+
+    QWidget *opts = new QWidget;
+    QGridLayout *optLayout = new QGridLayout(opts);
+    optLayout->setMargin(0);
+    layout->addWidget(opts, ++row, 0);
+
+    m_findWidget = new FindWidget(this);
+    optLayout->addWidget(m_findWidget, 0, 0, Qt::AlignLeft);
+    connect(m_findWidget, SIGNAL(findTextChanged(QString)),
+            this, SLOT(setSearchText(QString)));
+
+    m_showAllFiles = new QCheckBox(tr("Show all file states"), this);
+    m_showAllFiles->setEnabled(false);
+    optLayout->addWidget(m_showAllFiles, 0, 1, Qt::AlignRight);
+
+    QSettings settings;
+    m_showAllFiles->setChecked(settings.value("showall", false).toBool());
+
+    connect(m_showAllFiles, SIGNAL(toggled(bool)),
+            this, SIGNAL(showAllChanged()));
+}
+
+FileStatusWidget::~FileStatusWidget()
+{
+    QSettings settings;
+    settings.setValue("showall", m_showAllFiles->isChecked());
+
+    delete m_dateReference;
+}
+
+bool FileStatusWidget::shouldShowAll() const
+{
+    return m_showAllFiles->isChecked();
+}
+
+bool FileStatusWidget::shouldShow(FileStates::State s) const
+{
+    if (shouldShowAll()) return true;
+    else return (s != FileStates::Clean &&
+                 s != FileStates::Ignored);
+}
+
+QString FileStatusWidget::labelFor(FileStates::State s, bool addHighlightExplanation)
+{
+    QSettings settings;
+    settings.beginGroup("Presentation");
+    if (settings.value("showhelpfultext", true).toBool()) {
+        if (addHighlightExplanation) {
+            return QString("<qt><b>%1</b><br>%2<br>%3</qt>")
+                .arg(m_simpleLabels[s])
+                .arg(m_descriptions[s])
+                .arg(m_highlightExplanation);
+        } else {
+            return QString("<qt><b>%1</b><br>%2</qt>")
+                .arg(m_simpleLabels[s])
+                .arg(m_descriptions[s]);
+        }
+    }
+    return QString("<qt><b>%1</b></qt>")
+        .arg(m_simpleLabels[s]);
+}
+
+void FileStatusWidget::setNoModificationsLabelText()
+{
+    QSettings settings;
+    settings.beginGroup("Presentation");
+
+    if (m_searchText != "") {
+        if (!m_showAllFiles->isChecked()) {
+            m_noModificationsLabel->setText
+                (tr("<qt><b>Nothing found</b><br>None of the modified files have matching filenames.<br>Select <b>Show all file states</b> to find matches among unmodified and untracked files as well.</qt>"));
+        } else {
+            m_noModificationsLabel->setText
+                (tr("<qt><b>Nothing found</b><br>No files have matching filenames.</qt>"));
+        }
+    } else if (settings.value("showhelpfultext", true).toBool()) {
+        m_noModificationsLabel->setText
+            (tr("<qt>This area will list files in your working folder that you have changed.<br><br>At the moment you have no uncommitted changes.<br><br>To see changes previously made to the repository,<br>switch to the History tab.<br><br>%1</qt>")
+#if defined Q_OS_MAC
+             .arg(tr("To open the working folder in Finder,<br>click on the &ldquo;Local&rdquo; folder path shown above."))
+#elif defined Q_OS_WIN32
+             .arg(tr("To open the working folder in Windows Explorer,<br>click on the &ldquo;Local&rdquo; folder path shown above."))
+#else
+             .arg(tr("To open the working folder in your system file manager,<br>click the &ldquo;Local&rdquo; folder path shown above."))
+#endif
+                );
+    } else {
+        m_noModificationsLabel->setText
+            (tr("<qt>You have no uncommitted changes.</qt>"));
+    }
+}
+
+
+void FileStatusWidget::menuActionActivated()
+{
+    QAction *act = qobject_cast<QAction *>(sender());
+    if (!act) return;
+    
+    FileStates::State state = (FileStates::State)
+        act->property("state").toUInt();
+    FileStates::Activity activity = (FileStates::Activity)
+        act->property("activity").toUInt();
+
+    DEBUG << "menuActionActivated: state = " << state << ", activity = "
+          << activity << endl;
+
+    if (!FileStates::supportsActivity(state, activity)) {
+        std::cerr << "WARNING: FileStatusWidget::menuActionActivated: "
+                  << "Action state " << state << " does not support activity "
+                  << activity << std::endl;
+        return;
+    }
+
+    QStringList files = getSelectedFilesInState(state);
+
+    switch (activity) {
+    case FileStates::Annotate: emit annotateFiles(files); break;
+    case FileStates::Diff: emit diffFiles(files); break;
+    case FileStates::Commit: emit commitFiles(files); break;
+    case FileStates::Revert: emit revertFiles(files); break;
+    case FileStates::Rename: emit renameFiles(files); break;
+    case FileStates::Copy: emit copyFiles(files); break;
+    case FileStates::Add: emit addFiles(files); break;
+    case FileStates::Remove: emit removeFiles(files); break;
+    case FileStates::RedoMerge: emit redoFileMerges(files); break;
+    case FileStates::MarkResolved: emit markFilesResolved(files); break;
+    case FileStates::Ignore: emit ignoreFiles(files); break;
+    case FileStates::UnIgnore: emit unIgnoreFiles(files); break;
+    case FileStates::ShowIn: emit showIn(files); break;
+    }
+}
+
+void FileStatusWidget::itemDoubleClicked(QListWidgetItem *item)
+{
+    QStringList files;
+    QString file = item->text();
+    files << file;
+
+    switch (m_fileStates.stateOf(file)) {
+
+    case FileStates::Modified:
+    case FileStates::InConflict:
+        emit diffFiles(files);
+        break;
+
+    case FileStates::Clean:
+    case FileStates::Missing:
+        emit annotateFiles(files);
+        break;
+       
+    default:
+        break;
+    }
+}
+
+void FileStatusWidget::itemSelectionChanged()
+{
+    DEBUG << "FileStatusWidget::itemSelectionChanged" << endl;
+
+    QListWidget *list = qobject_cast<QListWidget *>(sender());
+
+    if (list) {
+        foreach (QListWidget *w, m_stateListMap) {
+            if (w != list) {
+                w->blockSignals(true);
+                w->clearSelection();
+                w->blockSignals(false);
+            }
+        }
+    }
+
+    m_selectedFiles.clear();
+
+    foreach (QListWidget *w, m_stateListMap) {
+        QList<QListWidgetItem *> sel = w->selectedItems();
+        foreach (QListWidgetItem *i, sel) {
+            m_selectedFiles.push_back(i->text());
+            DEBUG << "file " << i->text() << " is selected" << endl;
+        }
+    }
+
+    emit selectionChanged();
+}
+
+void FileStatusWidget::clearSelections()
+{
+    m_selectedFiles.clear();
+    foreach (QListWidget *w, m_stateListMap) {
+        w->clearSelection();
+    }
+}
+
+bool FileStatusWidget::haveChangesToCommit() const
+{
+    return !getAllCommittableFiles().empty();
+}
+
+bool FileStatusWidget::haveSelection() const
+{
+    return !m_selectedFiles.empty();
+}
+
+QStringList FileStatusWidget::getSelectedFilesInState(FileStates::State s) const
+{
+    QStringList files;
+    foreach (QString f, m_selectedFiles) {
+        if (m_fileStates.stateOf(f) == s) files.push_back(f);
+    }
+    return files;
+}    
+
+QStringList FileStatusWidget::getSelectedFilesSupportingActivity(FileStates::Activity a) const
+{
+    QStringList files;
+    foreach (QString f, m_selectedFiles) {
+        if (m_fileStates.supportsActivity(f, a)) files.push_back(f);
+    }
+    return files;
+}    
+
+QStringList FileStatusWidget::getAllCommittableFiles() const
+{
+    return m_fileStates.filesSupportingActivity(FileStates::Commit);
+}
+
+QStringList FileStatusWidget::getAllRevertableFiles() const
+{
+    return m_fileStates.filesSupportingActivity(FileStates::Revert);
+}
+
+QStringList FileStatusWidget::getAllUnresolvedFiles() const
+{
+    return m_fileStates.filesInState(FileStates::InConflict);
+}
+
+QStringList FileStatusWidget::getSelectedAddableFiles() const
+{
+    return getSelectedFilesSupportingActivity(FileStates::Add);
+}
+
+QStringList FileStatusWidget::getSelectedRemovableFiles() const
+{
+    return getSelectedFilesSupportingActivity(FileStates::Remove);
+}
+
+QString
+FileStatusWidget::localPath() const
+{
+    return m_localPath;
+}
+
+void
+FileStatusWidget::setLocalPath(QString p)
+{
+    m_localPath = p;
+    delete m_dateReference;
+    m_dateReference = new QFileInfo(p + "/.hg/dirstate");
+    if (!m_dateReference->exists() ||
+        !m_dateReference->isFile() ||
+        !m_dateReference->isReadable()) {
+        DEBUG << "FileStatusWidget::setLocalPath: date reference file "
+                << m_dateReference->absoluteFilePath()
+                << " does not exist, is not a file, or cannot be read"
+                << endl;
+        delete m_dateReference;
+        m_dateReference = 0;
+        m_showAllFiles->setEnabled(false);
+    } else {
+        m_showAllFiles->setEnabled(true);
+    }
+}
+
+void
+FileStatusWidget::setFileStates(FileStates p)
+{
+    m_fileStates = p;
+    updateWidgets();
+}
+
+void
+FileStatusWidget::setSearchText(QString text)
+{
+    if (m_searchText == text) return;
+    m_searchText = text;
+    updateWidgets();
+}
+
+void
+FileStatusWidget::clearWidgets()
+{
+    foreach (FileStates::State s, m_stateListMap.keys()) {
+        QListWidget *w = m_stateListMap[s];
+        w->clear();
+        w->parentWidget()->hide();
+    }
+}
+
+void
+FileStatusWidget::updateWidgets()
+{
+    QDateTime lastInteractionTime;
+    if (m_dateReference) {
+        lastInteractionTime = m_dateReference->lastModified();
+        DEBUG << "reference time: " << lastInteractionTime << endl;
+    }
+
+    QSet<QString> selectedFiles;
+    foreach (QString f, m_selectedFiles) selectedFiles.insert(f);
+
+    int visibleCount = 0;
+    bool finding = (m_searchText != "");
+
+    foreach (FileStates::State s, m_stateListMap.keys()) {
+
+        QListWidget *w = m_stateListMap[s];
+        w->clear();
+
+        if (!shouldShow(s)) {
+            w->parentWidget()->hide();
+            continue;
+        }
+
+        QStringList files = m_fileStates.filesInState(s);
+        bool foundSomething = false;
+
+        QStringList highPriority, lowPriority;
+
+        foreach (QString file, files) {
+
+            if (finding) {
+                if (file.contains(m_searchText, Qt::CaseInsensitive)) {
+                    highPriority.push_back(file);
+                    foundSomething = true;
+                }
+                continue;
+            } else {
+                foundSomething = true;
+            }
+
+            bool highlighted = false;
+
+            if (s == FileStates::Unknown) {
+                // We want to highlight untracked files that have appeared
+                // since the last interaction with the repo
+                QString fn(m_localPath + "/" + file);
+                DEBUG << "comparing with " << fn << endl;
+                QFileInfo fi(fn);
+                if (fi.exists() && fi.created() > lastInteractionTime) {
+                    DEBUG << "file " << fn << " is newer (" << fi.lastModified()
+                            << ") than reference" << endl;
+                    highlighted = true;
+                }
+            }
+
+            if (highlighted) {
+                highPriority.push_back(file);
+            } else {
+                lowPriority.push_back(file);
+            }
+        }
+
+        foreach (QString file, highPriority) {
+            QListWidgetItem *item = new QListWidgetItem(file);
+            w->addItem(item);
+            if (finding) {
+                item->setForeground(QColor("#008400"));
+            } else {
+                item->setForeground(QColor("#d40000"));
+            }                
+            item->setSelected(selectedFiles.contains(file));
+        }
+
+        foreach (QString file, lowPriority) {
+            QListWidgetItem *item = new QListWidgetItem(file);
+            w->addItem(item);
+            item->setSelected(selectedFiles.contains(file));
+        }
+
+        setLabelFor(w, s, !finding && !highPriority.empty());
+
+        if (!foundSomething) {
+            w->parentWidget()->hide();
+        } else {
+            w->parentWidget()->show();
+            ++visibleCount;
+        }
+    }
+
+    m_noModificationsLabel->setVisible(visibleCount == 0);
+
+    if (visibleCount > 3) {
+        layoutBoxesGridly(visibleCount);
+    } else {
+        layoutBoxesLinearly();
+    }
+
+    setNoModificationsLabelText();
+}
+
+void FileStatusWidget::layoutBoxesGridly(int visibleCount)
+{
+    if (m_gridlyLayout && m_lastGridlyCount == visibleCount) return;
+
+    delete m_boxesParent->layout();
+    
+    QGridLayout *layout = new QGridLayout;
+    layout->setMargin(0);
+    m_boxesParent->setLayout(layout);
+
+    int row = 0;
+    int col = 0;
+
+    DEBUG << "FileStatusWidget::layoutBoxesGridly: visibleCount = "
+          << visibleCount << endl;
+
+    for (int i = 0; i < m_boxes.size(); ++i) {
+
+        if (!m_boxes[i]->isVisible()) continue;
+
+        if (col == 0 && row >= (visibleCount+1)/2) {
+            layout->addItem(new QSpacerItem(10, 5), 0, 1);
+            col = 2;
+            row = 0;
+        }
+
+        layout->addWidget(m_boxes[i], row, col);
+
+        ++row;
+    }
+
+    m_gridlyLayout = true;
+    m_lastGridlyCount = visibleCount;
+}
+
+void FileStatusWidget::layoutBoxesLinearly()
+{
+    if (!m_gridlyLayout) return;
+
+    delete m_boxesParent->layout();
+    
+    QGridLayout *layout = new QGridLayout;
+    layout->setMargin(0);
+    m_boxesParent->setLayout(layout);
+
+    for (int i = 0; i < m_boxes.size(); ++i) {
+        layout->addWidget(m_boxes[i], i, 0);
+    }
+
+    m_gridlyLayout = false;
+}
+
+void FileStatusWidget::setLabelFor(QWidget *w, FileStates::State s, bool addHighlight)
+{
+    QString text = labelFor(s, addHighlight);
+    QWidget *p = w->parentWidget();
+    QList<QLabel *> ql = p->findChildren<QLabel *>();
+    if (!ql.empty()) ql[0]->setText(text);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/filestatuswidget.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,126 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 FILESTATUSWIDGET_H
+#define FILESTATUSWIDGET_H
+
+#include "filestates.h"
+
+#include <QWidget>
+#include <QList>
+
+class QLabel;
+class QListWidget;
+class QListWidgetItem;
+class QPushButton;
+class QFileInfo;
+class QCheckBox;
+class FindWidget;
+
+class FileStatusWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    FileStatusWidget(QWidget *parent = 0);
+    ~FileStatusWidget();
+
+    QString localPath() const;
+    void setLocalPath(QString p);
+
+    FileStates fileStates() const;
+    void setFileStates(FileStates sp);
+
+    bool haveChangesToCommit() const;
+    bool haveSelection() const;
+
+    QStringList getAllCommittableFiles() const;
+    QStringList getAllRevertableFiles() const;
+    QStringList getAllUnresolvedFiles() const;
+
+    QStringList getSelectedAddableFiles() const;
+    QStringList getSelectedRemovableFiles() const;
+
+    bool shouldShowAll() const;
+    bool shouldShow(FileStates::State) const;
+
+signals:
+    void selectionChanged();
+    void showAllChanged();
+
+    void annotateFiles(QStringList);
+    void diffFiles(QStringList);
+    void commitFiles(QStringList);
+    void revertFiles(QStringList);
+    void renameFiles(QStringList);
+    void copyFiles(QStringList);
+    void addFiles(QStringList);
+    void removeFiles(QStringList);
+    void redoFileMerges(QStringList);
+    void markFilesResolved(QStringList);
+    void ignoreFiles(QStringList);
+    void unIgnoreFiles(QStringList);
+    void showIn(QStringList);
+
+public slots:
+    void clearSelections();
+    void updateWidgets();
+    void clearWidgets(); // e.g. while cloning a new repo slowly
+
+    void setSearchText(QString text);
+
+private slots:
+    void menuActionActivated();
+    void itemSelectionChanged();
+    void itemDoubleClicked(QListWidgetItem *);
+
+private:
+    QString m_localPath;
+    QLabel *m_noModificationsLabel;
+
+    FindWidget *m_findWidget;
+    QCheckBox *m_showAllFiles;
+    
+    FileStates m_fileStates;
+    QMap<FileStates::State, QString> m_simpleLabels;
+    QMap<FileStates::State, QString> m_descriptions;
+    QMap<FileStates::State, QListWidget *> m_stateListMap;
+    QMap<FileStates::Activity, QString> m_actionLabels;
+    QMap<FileStates::Activity, QString> m_shortcuts;
+    QString m_highlightExplanation;
+
+    QFileInfo *m_dateReference;
+    QStringList m_selectedFiles;
+
+    bool m_gridlyLayout;
+    int m_lastGridlyCount;
+    QList<QWidget *> m_boxes;
+    QWidget *m_boxesParent;
+
+    QString m_searchText;
+
+    void layoutBoxesGridly(int count);
+    void layoutBoxesLinearly();
+    void setNoModificationsLabelText();
+    QString labelFor(FileStates::State, bool addHighlightExplanation = false);
+    void setLabelFor(QWidget *w, FileStates::State, bool addHighlightExplanation);
+
+    QStringList getSelectedFilesInState(FileStates::State s) const;
+    QStringList getSelectedFilesSupportingActivity(FileStates::Activity) const;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/findwidget.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,77 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "findwidget.h"
+
+#include <QGridLayout>
+#include <QLabel>
+#include <QLineEdit>
+#include <QToolButton>
+
+FindWidget::FindWidget(QWidget *parent) :
+    QWidget(parent)
+{
+    QGridLayout *layout = new QGridLayout;
+    layout->setMargin(0);
+    setLayout(layout);
+
+    QToolButton *button = new QToolButton();
+    layout->addWidget(button, 0, 0);
+    button->setText(tr("Find..."));
+    button->setToolButtonStyle(Qt::ToolButtonTextOnly);
+    button->setShortcut(tr("Ctrl+F"));
+    connect(button, SIGNAL(clicked()), this, SLOT(buttonPressed()));
+
+    m_lineEdit = new QLineEdit();
+    layout->addWidget(m_lineEdit, 0, 1);
+
+    m_lineEdit->setFixedWidth(100);
+    m_lineEdit->hide();
+
+    int h = m_lineEdit->sizeHint().height();
+    int h0 = button->sizeHint().height();
+    if (h > h0) button->setFixedHeight(h);
+
+    connect(m_lineEdit, SIGNAL(textChanged(const QString &)),
+	    this, SIGNAL(findTextChanged(QString)));
+}
+
+FindWidget::~FindWidget()
+{
+}
+
+void
+FindWidget::buttonPressed()
+{
+    QAbstractButton *button = qobject_cast<QAbstractButton *>(sender());
+    if (!button) return;
+    if (m_lineEdit->isVisible()) {
+        m_lineEdit->hide();
+        button->setText(tr("Find..."));
+        if (m_lineEdit->text() != "") {
+            emit findTextChanged("");
+        }
+    } else {
+        m_lineEdit->show();
+        m_lineEdit->setFocus(Qt::OtherFocusReason);
+        button->setText(tr("Find:"));
+        if (m_lineEdit->text() != "") {
+            emit findTextChanged(m_lineEdit->text());
+        }
+    }
+}
+	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/findwidget.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,43 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 FINDWIDGET_H
+#define FINDWIDGET_H
+
+#include <QWidget>
+
+class QLineEdit;
+
+class FindWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    FindWidget(QWidget *parent = 0);
+    ~FindWidget();
+
+signals:
+    void findTextChanged(QString);
+
+private slots:
+    void buttonPressed();
+
+private:
+    QLineEdit *m_lineEdit;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/fswatcher.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,462 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 <QMutexLocker>
+#include <QDir>
+
+#ifdef Q_OS_MAC
+// Must include this before debug.h
+#include <CoreServices/CoreServices.h>
+#endif
+
+#include "fswatcher.h"
+#include "debug.h"
+
+#include <deque>
+
+//#define DEBUG_FSWATCHER 1
+
+/*
+ * Watching the filesystem is trickier than it seems at first glance.
+ *
+ * We ideally should watch every directory, and every file that is
+ * tracked by Hg. If a new file is created in a directory, then we
+ * need to respond in order to show it as a potential candidate to be
+ * added.
+ *
+ * Complicating matters though is that Hg itself might modify the
+ * filesystem. This can happen even in "read-only" operations: for
+ * example, hg stat creates files called hg-checklink and hg-checkexec
+ * to test properties of the filesystem. So we need to know to ignore
+ * those files; unfortunately, when watching a directory (which is how
+ * we find out about the creation of new files) we are notified only
+ * that the directory has changed -- we aren't told what changed.
+ *
+ * This means that, when a directory changes, we need to rescan the
+ * directory to learn whether the set of files in it _excluding_ files
+ * matching our ignore patterns differs from the previous scan, and
+ * ignore the change if it doesn't.
+ *
+ */
+
+/*
+ * 20120312 -- Another complication. The documentation for
+ * QFileSystemWatcher says:
+ *
+ *     On Mac OS X 10.4 [...] an open file descriptor is required for
+ *     each monitored file. [...] This means that addPath() and
+ *     addPaths() will fail if your process tries to add more than 256
+ *     files or directories to the file system monitor [...] Mac OS X
+ *     10.5 and up use a different backend and do not suffer from this
+ *     issue.
+ * 
+ * Unfortunately, the last sentence above is not true:
+ * http://qt.gitorious.org/qt/qt/commit/6d1baf9979346d6f15da81a535becb4046278962
+ * ("Removing the usage of FSEvents-based backend for now as it has a
+ * few bugs...").  It can't be restored without hacking the Qt source,
+ * which we don't want to do in this context. The commit log doesn't
+ * make clear how serious the bugs were -- an example is given but it
+ * doesn't indicate whether it's an edge case or a common case and
+ * whether the result was a crash or failure to notify.
+ *
+ * This means the Qt class uses kqueue instead on OS/X, but that
+ * doesn't really work for us -- it can only monitor 256 files (or
+ * whatever the fd ulimit is set to, but that's the default) and it
+ * doesn't notify if a file within a directory is modified unless the
+ * metadata changes. The main limitation of FSEvents is that it only
+ * notifies with directory granularity, but that might be OK for us so
+ * long as notifications are actually provoked by file changes as
+ * well. (In OS/X 10.7 there appear to be file-level notifications
+ * too, but that doesn't help us.)
+ *
+ * One other problem with FSEvents is that the API only exists on OS/X
+ * 10.5 or newer -- on older versions we would have no option but to
+ * use kqueue via QFileSystemWatcher. But we can't ship a binary
+ * linked with the FSEvents API to run on 10.4 without some fiddling,
+ * and I'm not really keen to do that either.  That may be our cue to
+ * drop 10.4 support for EasyMercurial.
+ */
+
+FsWatcher::FsWatcher() :
+    m_lastToken(0),
+    m_lastCounter(0)
+{
+#ifdef Q_OS_MAC
+    m_stream = 0; // create when we have a path
+#else
+    connect(&m_watcher, SIGNAL(directoryChanged(QString)),
+	    this, SLOT(fsDirectoryChanged(QString)));
+    connect(&m_watcher, SIGNAL(fileChanged(QString)),
+	    this, SLOT(fsFileChanged(QString)));
+#endif
+}
+
+FsWatcher::~FsWatcher()
+{
+}
+
+void
+FsWatcher::setWorkDirPath(QString path)
+{
+    QMutexLocker locker(&m_mutex);
+    if (m_workDirPath == path) return;
+    clearWatchedPaths();
+    m_workDirPath = path;
+    addWorkDirectory(path);
+    debugPrint();
+}
+
+void
+FsWatcher::clearWatchedPaths()
+{
+#ifdef Q_OS_MAC
+    FSEventStreamRef stream = (FSEventStreamRef)m_stream;
+    if (stream) {
+        FSEventStreamStop(stream);
+        FSEventStreamInvalidate(stream);
+        FSEventStreamRelease(stream);
+    }
+    m_stream = 0;
+#else
+    // annoyingly, removePaths prints a warning if given an empty list
+    if (!m_watcher.directories().empty()) {
+        m_watcher.removePaths(m_watcher.directories());
+    }
+    if (!m_watcher.files().empty()) {
+        m_watcher.removePaths(m_watcher.files());
+    }
+#endif
+}
+
+#ifdef Q_OS_MAC
+static void
+fsEventsCallback(ConstFSEventStreamRef streamRef,
+                 void *clientCallBackInfo,
+                 size_t numEvents,
+                 void *paths,
+                 const FSEventStreamEventFlags eventFlags[],
+                 const FSEventStreamEventId eventIDs[])
+{
+    FsWatcher *watcher = reinterpret_cast<FsWatcher *>(clientCallBackInfo);
+    const char *const *cpaths = reinterpret_cast<const char *const *>(paths);
+    for (size_t i = 0; i < numEvents; ++i) {
+#ifdef DEBUG_FSWATCHER
+        std::cerr << "path " << i << " = " << cpaths[i] << std::endl;
+#endif
+        watcher->fsDirectoryChanged(QString::fromLocal8Bit(cpaths[i]));
+    }
+}
+#endif
+
+void
+FsWatcher::addWorkDirectory(QString path)
+{
+#ifdef Q_OS_MAC
+
+    CFStringRef cfPath = CFStringCreateWithCharacters
+        (0, reinterpret_cast<const UniChar *>(path.unicode()),
+         path.length());
+
+    CFArrayRef cfPaths = CFArrayCreate(0, (const void **)&cfPath, 1, 0);
+
+    FSEventStreamContext ctx = { 0, 0, 0, 0, 0 };
+    ctx.info = this;
+
+    FSEventStreamRef stream =
+        FSEventStreamCreate(kCFAllocatorDefault,
+                            &fsEventsCallback,
+                            &ctx,
+                            cfPaths,
+                            kFSEventStreamEventIdSinceNow,
+                            1.0, // latency, seconds
+                            kFSEventStreamCreateFlagNone);
+
+    m_stream = stream;
+    
+    FSEventStreamScheduleWithRunLoop(stream,
+                                     CFRunLoopGetCurrent(),
+                                     kCFRunLoopDefaultMode);
+
+    if (!FSEventStreamStart(stream)) {
+        std::cerr << "ERROR: FsWatcher::addWorkDirectory: Failed to start FSEvent stream" << std::endl;
+    }
+#else
+    // QFileSystemWatcher will refuse to add a file or directory to
+    // its watch list that it is already watching -- fine -- but it
+    // prints a warning when this happens, which we wouldn't want.  So
+    // we'll check for duplicates ourselves.
+    QSet<QString> alreadyWatched = 
+	QSet<QString>::fromList(m_watcher.directories());
+    
+    std::deque<QString> pending;
+    pending.push_back(path);
+
+    while (!pending.empty()) {
+
+        QString path = pending.front();
+        pending.pop_front();
+        if (!alreadyWatched.contains(path)) {
+            m_watcher.addPath(path);
+            m_dirContents[path] = scanDirectory(path);
+        }
+
+        QDir d(path);
+        if (d.exists()) {
+            d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot |
+                        QDir::Readable | QDir::NoSymLinks);
+            foreach (QString entry, d.entryList()) {
+                if (entry.startsWith('.')) continue;
+                QString entryPath = d.absoluteFilePath(entry);
+                pending.push_back(entryPath);
+            }
+        }
+    }
+#endif
+}
+
+void
+FsWatcher::setTrackedFilePaths(QStringList paths)
+{
+#ifdef Q_OS_MAC
+
+    // FSEvents will notify when any file in the directory changes,
+    // but we need to be able to check whether the file change was
+    // meaningful to us if it didn't result in any files being added
+    // or removed -- and we have to do that by examining timestamps on
+    // the files we care about
+    foreach (QString p, paths) {
+        m_trackedFileUpdates[p] = QDateTime::currentDateTime();
+    }
+
+#else
+
+    QMutexLocker locker(&m_mutex);
+
+    QSet<QString> alreadyWatched = 
+	QSet<QString>::fromList(m_watcher.files());
+
+    foreach (QString path, paths) {
+        path = m_workDirPath + QDir::separator() + path;
+        if (!alreadyWatched.contains(path)) {
+            m_watcher.addPath(path);
+        } else {
+            alreadyWatched.remove(path);
+        }
+    }
+
+    // Remove the remaining paths, those that were being watched
+    // before but that are not in the list we were given
+    foreach (QString path, alreadyWatched) {
+        m_watcher.removePath(path);
+    }
+
+    debugPrint();
+
+#endif
+}
+
+void
+FsWatcher::setIgnoredFilePrefixes(QStringList prefixes)
+{
+    QMutexLocker locker(&m_mutex);
+    m_ignoredPrefixes = prefixes;
+}
+
+void
+FsWatcher::setIgnoredFileSuffixes(QStringList suffixes)
+{
+    QMutexLocker locker(&m_mutex);
+    m_ignoredSuffixes = suffixes;
+}
+
+int
+FsWatcher::getNewToken()
+{
+    QMutexLocker locker(&m_mutex);
+    int token = ++m_lastToken;
+    m_tokenMap[token] = m_lastCounter;
+    return token;
+}
+
+QSet<QString>
+FsWatcher::getChangedPaths(int token)
+{
+    QMutexLocker locker(&m_mutex);
+    size_t lastUpdatedAt = m_tokenMap[token];
+    QSet<QString> changed;
+    for (QHash<QString, size_t>::const_iterator i = m_changes.begin();
+         i != m_changes.end(); ++i) {
+        if (i.value() > lastUpdatedAt) {
+             changed.insert(i.key());
+        }
+    }
+    m_tokenMap[token] = m_lastCounter;
+    return changed;
+}
+
+void
+FsWatcher::fsDirectoryChanged(QString path)
+{
+    bool haveChanges = false;
+
+    {
+	QMutexLocker locker(&m_mutex);
+
+	if (shouldIgnore(path)) return;
+
+        QSet<QString> files = scanDirectory(path);
+
+        if (files == m_dirContents[path]) {
+
+#ifdef DEBUG_FSWATCHER
+            std::cerr << "FsWatcher: Directory " << path << " has changed, but not in a way that we are monitoring -- doing manual check" << std::endl;
+#endif
+
+#ifdef Q_OS_MAC
+            haveChanges = manuallyCheckTrackedFiles();
+#endif
+
+        } else {
+
+#ifdef DEBUG_FSWATCHER
+            std::cerr << "FsWatcher: Directory " << path << " has changed" << std::endl;
+#endif
+            m_dirContents[path] = files;
+            size_t counter = ++m_lastCounter;
+            m_changes[path] = counter;
+            haveChanges = true;
+        }
+    }
+
+    if (haveChanges) {
+        emit changed();
+    }
+}
+
+void
+FsWatcher::fsFileChanged(QString path)
+{
+    {
+        QMutexLocker locker(&m_mutex);
+
+        // We don't check whether the file matches an ignore pattern,
+        // because we are only notified for file changes if we are
+        // watching the file explicitly, i.e. the file is in the
+        // tracked file paths list. So we never want to ignore these
+
+#ifdef DEBUG_FSWATCHER
+        std::cerr << "FsWatcher: Tracked file " << path << " has changed" << std::endl;
+#endif
+
+        size_t counter = ++m_lastCounter;
+        m_changes[path] = counter;
+    }
+
+    emit changed();
+}
+
+#ifdef Q_OS_MAC
+bool
+FsWatcher::manuallyCheckTrackedFiles()
+{
+#ifdef DEBUG_FSWATCHER
+    std::cerr << "FsWatcher::manuallyCheckTrackedFiles" << std::endl;
+#endif
+    bool foundChanges = false;
+
+    for (PathTimeMap::iterator i = m_trackedFileUpdates.begin();
+         i != m_trackedFileUpdates.end(); ++i) {
+
+        QString path = i.key();
+        QDateTime prevUpdate = i.value();
+
+        QFileInfo fi(m_workDirPath + QDir::separator() + path);
+        QDateTime currUpdate = fi.lastModified();
+
+//        std::cerr << "FsWatcher: Tracked file " << path << " previously changed at "
+//                  << prevUpdate.toString().toStdString()
+//                  << ", currently at " << currUpdate.toString().toStdString() << std::endl;
+
+        if (currUpdate > prevUpdate) {
+
+#ifdef DEBUG_FSWATCHER
+            std::cerr << "FsWatcher: Tracked file " << path << " has been changed since last check" << std::endl;
+#endif
+            i.value() = currUpdate;
+            
+            size_t counter = ++m_lastCounter;
+            m_changes[path] = counter;
+            foundChanges = true;
+        }
+    }
+    
+    return foundChanges;
+}
+#endif
+
+bool
+FsWatcher::shouldIgnore(QString path)
+{
+    QFileInfo fi(path);
+    QString fn(fi.fileName());
+    foreach (QString pfx, m_ignoredPrefixes) {
+        if (fn.startsWith(pfx)) {
+#ifdef DEBUG_FSWATCHER
+            std::cerr << "(ignoring: " << path << ")" << std::endl;
+#endif
+            return true;
+        }
+    }
+    foreach (QString sfx, m_ignoredSuffixes) {
+        if (fn.endsWith(sfx)) {
+#ifdef DEBUG_FSWATCHER
+            std::cerr << "(ignoring: " << path << ")" << std::endl;
+#endif
+            return true;
+        }
+    }
+    return false;
+}
+
+QSet<QString>
+FsWatcher::scanDirectory(QString path)
+{
+    QSet<QString> files;
+    QDir d(path);
+    if (d.exists()) {
+        d.setFilter(QDir::Files | QDir::NoDotAndDotDot |
+                    QDir::Readable | QDir::NoSymLinks);
+        foreach (QString entry, d.entryList()) {
+            if (entry.startsWith('.')) continue;
+            if (shouldIgnore(entry)) continue;
+            files.insert(entry);
+        }
+    }
+    return files;
+}
+
+void
+FsWatcher::debugPrint()
+{
+#ifdef DEBUG_FSWATCHER
+#ifndef Q_OS_MAC
+    std::cerr << "FsWatcher: Now watching " << m_watcher.directories().size()
+              << " directories and " << m_watcher.files().size()
+              << " files" << std::endl;
+#endif
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/fswatcher.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,165 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 FSWATCHER_H
+#define FSWATCHER_H
+
+#include <QObject>
+#include <QMutex>
+#include <QString>
+#include <QSet>
+#include <QHash>
+#include <QMap>
+#include <QDateTime>
+#include <QStringList>
+
+#ifndef Q_OS_MAC
+// We don't use QFileSystemWatcher on OS/X.
+// See comments at top of fswatcher.cpp for an explanation.
+#include <QFileSystemWatcher>
+#endif
+
+class FsWatcher : public QObject
+{
+    Q_OBJECT
+
+public:
+    FsWatcher();
+    virtual ~FsWatcher();
+
+    /**
+     * Set the root path of the work directory to be monitored. This
+     * directory and all its subdirectories (recursively) will be
+     * monitored for changes.
+     *
+     * If this path differs from the currently set work dir path, then
+     * the tracked file paths will also be cleared. Call
+     * setTrackedFilePaths afterwards to ensure non-directory files
+     * are monitored.
+     */
+    void setWorkDirPath(QString path);
+
+    /**
+     * Provide a set of paths for files which should be tracked. These
+     * will be the only non-directory files monitored for changes. The
+     * paths should be relative to the work directory.
+     */
+    void setTrackedFilePaths(QStringList paths);
+    
+    /**
+     * Provide a set of prefixes to ignore. Files whose names start
+     * with a prefix in this set will be ignored when they change.
+     */
+    void setIgnoredFilePrefixes(QStringList prefixes);
+
+    /**
+     * Provide a set of suffixes to ignore. Files whose names end
+     * with a suffix in this set will be ignored when they change.
+     */
+    void setIgnoredFileSuffixes(QStringList suffixes);
+
+    /**
+     * Return a token to identify the current caller in subsequent
+     * calls to getChangedPaths().  Only changes that occur after this
+     * has been called can be detected by the caller.
+     */
+    int getNewToken();
+    
+    /**
+     * Return a list of all non-ignored file paths that have been
+     * observed to have changed since the last call to getChangedPaths
+     * with the same token.
+     */
+    QSet<QString> getChangedPaths(int token);
+    
+signals:
+    /**
+     * Emitted when something has changed. Use the asynchronous
+     * interface to find out what.
+     */
+    void changed();
+
+public slots:
+    void fsDirectoryChanged(QString);
+    void fsFileChanged(QString);
+
+private:
+    // call with lock already held
+    void clearWatchedPaths();
+
+    // call with lock already held
+    void addWorkDirectory(QString path);
+
+    // call with lock already held
+    bool shouldIgnore(QString path);
+
+    // call with lock already held. Returns set of non-ignored files in dir
+    QSet<QString> scanDirectory(QString path);
+
+    // call with lock already held
+    void debugPrint();
+
+private:
+    /**
+     * A change associates a filename with a counter (the
+     * size_t). Each time a file is changed, its counter is assigned
+     * from m_lastCounter and m_lastCounter is incremented.
+     *
+     * This is not especially efficient -- we often want to find "all
+     * files whose counter is greater than X" which involves a
+     * traversal. Maybe something better later.
+     */
+    QHash<QString, size_t> m_changes;
+
+    /**
+     * Associates a token (the client identifier) with a counter. The
+     * counter represents the value of m_lastCounter at the moment
+     * getChangedPaths last completed for that token. Any files in
+     * m_changes whose counters are larger must have been modified.
+     */
+    QMap<int, size_t> m_tokenMap;
+
+    /**
+     * Associates a directory path with a list of all the files in it
+     * that do not match our ignore patterns. When a directory is
+     * signalled as having changed, then we need to rescan it and
+     * compare against this list before we can determine whether to
+     * notify about the change or not.
+     */
+    QHash<QString, QSet<QString> > m_dirContents;
+
+    QStringList m_ignoredPrefixes;
+    QStringList m_ignoredSuffixes;
+
+    /// Everything in this class is synchronised.
+    QMutex m_mutex;
+
+    QString m_workDirPath;
+    int m_lastToken;
+    size_t m_lastCounter;
+
+#ifdef Q_OS_MAC
+    void *m_stream;
+    typedef QMap<QString, QDateTime> PathTimeMap;
+    PathTimeMap m_trackedFileUpdates;
+    bool manuallyCheckTrackedFiles();
+#else
+    QFileSystemWatcher m_watcher;
+#endif
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/grapher.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,740 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "grapher.h"
+#include "connectionitem.h"
+#include "debug.h"
+#include "changesetscene.h"
+
+#include <QSettings>
+
+#include <iostream>
+
+Grapher::Grapher(ChangesetScene *scene) :
+    m_scene(scene)
+{
+    QSettings settings;
+    settings.beginGroup("Presentation");
+    m_showDates = (settings.value("dateformat", 0) == 1);
+    m_showClosedBranches = (settings.value("showclosedbranches", false).toBool());
+}
+
+int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol)
+{
+    int col = parent;
+    if (preferParentCol) {
+        if (isAvailable(row, col)) return col;
+    }
+    while (col > 0) {
+        if (isAvailable(row, --col)) return col;
+    }
+    while (col < 0) {
+        if (isAvailable(row, ++col)) return col;
+    }
+    col = parent;
+    int sign = (col < 0 ? -1 : 1);
+    while (1) {
+        col += sign;
+        if (isAvailable(row, col)) return col;
+    }
+}
+
+bool Grapher::isAvailable(int row, int col)
+{
+    if (m_alloc.contains(row) && m_alloc[row].contains(col)) return false;
+    if (!m_haveAllocatedUncommittedColumn) return true;
+    if (!m_uncommitted) return true;
+    return !(row <= m_uncommittedParentRow && col == m_uncommitted->column());
+}
+
+void Grapher::layoutRow(QString id)
+{
+    if (m_handled.contains(id)) {
+        return;
+    }
+    if (!m_changesets.contains(id)) {
+        throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
+    }
+    if (!m_items.contains(id)) {
+        return;
+    }
+    Changeset *cs = m_changesets[id];
+    ChangesetItem *item = m_items[id];
+    DEBUG << "layoutRow: Looking at " << id.toStdString() << endl;
+
+    int row = 0;
+    int nparents = cs->parents().size();
+
+    if (nparents > 0) {
+        bool haveRow = false;
+        foreach (QString parentId, cs->parents()) {
+
+            if (!m_changesets.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;
+            }
+        }
+        row = row - 1;
+    }
+
+    // row is now an upper bound on our eventual row (because we want
+    // to be above all parents).  But we also want to ensure we are
+    // above all nodes that have earlier dates (to the nearest day).
+    // m_rowDates maps each row to a date: use that.
+
+    QString date;
+    if (m_showDates) {
+        date = cs->date();
+    } else {
+        date = cs->age();
+    }
+    while (m_rowDates.contains(row) && m_rowDates[row] != date) {
+        --row;
+    }
+
+    // We have already laid out all nodes that have earlier timestamps
+    // than this one, so we know (among other things) that we can
+    // safely fill in this row has having this date, if it isn't in
+    // the map yet (it cannot have an earlier date)
+
+    if (!m_rowDates.contains(row)) {
+        m_rowDates[row] = date;
+    }
+
+    // If we're the parent of the uncommitted item, make a note of our
+    // row (we need it later, to avoid overwriting the connecting line)
+    if (!m_uncommittedParents.empty() && m_uncommittedParents[0] == id) {
+        m_uncommittedParentRow = row;
+    }
+
+    DEBUG << "putting " << cs->id().toStdString() << " at row " << row 
+          << endl;
+
+    item->setRow(row);
+    m_handled.insert(id);
+}
+
+void Grapher::layoutCol(QString id)
+{
+    if (m_handled.contains(id)) {
+        DEBUG << "Already looked at " << id.toStdString() << endl;
+        return;
+    }
+    if (!m_changesets.contains(id)) {
+        throw LayoutException(QString("Changeset %1 not in ID map").arg(id));
+    }
+    if (!m_items.contains(id)) {
+        return;
+    }
+
+    Changeset *cs = m_changesets[id];
+    DEBUG << "layoutCol: Looking at " << id.toStdString() << endl;
+
+    ChangesetItem *item = m_items[id];
+
+    int col = 0;
+    int row = item->row();
+    QString branch = cs->branch();
+
+    int nparents = cs->parents().size();
+    QString parentId;
+    int parentsOnSameBranch = 0;
+
+    switch (nparents) {
+
+    case 0:
+        col = m_branchHomes[cs->branch()];
+        col = findAvailableColumn(row, col, true);
+        break;
+
+    case 1:
+        parentId = cs->parents()[0];
+
+        if (!m_changesets.contains(parentId) ||
+            !m_changesets[parentId]->isOnBranch(branch)) {
+            // new branch
+            col = m_branchHomes[branch];
+        } else if (m_items.contains(parentId)) {
+            col = m_items[parentId]->column();
+        }
+
+        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]->isOnBranch(branch)) {
+                if (m_items.contains(parentId)) {
+                    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;
+    }
+
+    DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl;
+
+    m_alloc[row].insert(col);
+    item->setColumn(col);
+    m_handled.insert(id);
+
+    // If we're the first parent of the uncommitted item, it should be
+    // given the same column as us (we already noted that its
+    // connecting line would end at our row)
+
+    if (m_uncommittedParents.contains(id)) {
+        if (m_uncommittedParents[0] == id) {
+            int ucol = findAvailableColumn(row-1, col, true);
+            m_uncommitted->setColumn(ucol);
+            m_haveAllocatedUncommittedColumn = true;
+        }
+        // also, if the uncommitted item has a different branch from
+        // any of its parents, tell it to show the branch
+        if (!cs->isOnBranch(m_uncommitted->branch())) {
+            DEBUG << "Uncommitted branch " << m_uncommitted->branch()
+                  << " differs from my branch " << cs->branch()
+                  << ", asking it to show branch" << endl;
+            m_uncommitted->setShowBranch(true);
+        }
+    }
+
+
+    // Normally the children will lay out themselves, but we can do
+    // a better job in some special cases:
+
+    int nchildren = cs->children().size();
+
+    // look for merging children and children distant from us but in a
+    // straight line, and make sure nobody is going to overwrite their
+    // connection lines
+
+    foreach (QString childId, cs->children()) {
+        DEBUG << "reserving connection line space" << endl;
+        if (!m_items.contains(childId)) continue;
+        Changeset *child = m_changesets[childId];
+        int childRow = m_items[childId]->row();
+        if (child->parents().size() > 1 ||
+            child->isOnBranch(cs->branch())) {
+            for (int r = row-1; r > childRow; --r) {
+                m_alloc[r].insert(col);
+            }
+        }
+    }
+
+    // look for the case where exactly two children have the same
+    // branch as us: split them to a little either side of our position
+
+    if (nchildren > 1) {
+        QList<QString> special;
+        foreach (QString childId, cs->children()) {
+            if (!m_items.contains(childId)) continue;
+            Changeset *child = m_changesets[childId];
+            if (child->isOnBranch(branch) &&
+                child->parents().size() == 1) {
+                special.push_back(childId);
+            }
+        }
+        if (special.size() == 2) {
+            DEBUG << "handling split-in-two for children " << special[0] << " and " << special[1] << endl;
+            for (int i = 0; i < 2; ++i) {
+                int off = i * 2 - 1; // 0 -> -1, 1 -> 1
+                ChangesetItem *it = m_items[special[i]];
+                it->setColumn(findAvailableColumn(it->row(), col + off, true));
+                for (int r = row-1; r >= it->row(); --r) {
+                    m_alloc[r].insert(it->column());
+                }
+                m_handled.insert(special[i]);
+            }
+        }
+    }
+}
+
+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 id = cs->id();
+        if (!m_items.contains(id)) continue;
+        ChangesetItem *item = m_items[id];
+        QString branch = cs->branch();
+        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;
+    m_branchHomes["default"] = 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) {
+                if (home % 2 == 1) {
+                    home = -home;
+                } else {
+                    home = home + 1;
+                }
+            } else {
+                if ((-home) % 2 == 1) {
+                    home = home + 1;
+                } else {
+                    home = -(home-2);
+                }
+            }
+        }
+        m_branchHomes[branch] = home;
+    }
+
+    foreach (QString branch, m_branchRanges.keys()) {
+        DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl;
+    }
+}
+
+static bool
+compareChangesetsByDate(Changeset *const &a, Changeset *const &b)
+{
+    return a->timestamp() < b->timestamp();
+}
+
+ChangesetItem *
+Grapher::getItemFor(Changeset *cs)
+{
+    if (!cs) return 0;
+    return getItemFor(cs->id());
+}
+
+ChangesetItem *
+Grapher::getItemFor(QString id)
+{
+    if (!m_items.contains(id)) return 0;
+    return m_items[id];
+}
+
+void
+Grapher::markClosedChangesets()
+{
+    // Ensure the closed branch changesets are all marked as closed.
+
+    QSet<QString> deferred;
+
+    foreach (QString id, m_closedIds) {
+        markClosedChangesetsFrom(id, deferred);
+//        std::cerr << "after closed id " << id << ": candidates now contains " << deferred.size() << " element(s)" << std::endl;
+    }
+
+    while (!deferred.empty()) {
+        foreach (QString id, deferred) {
+            markClosedChangesetsFrom(id, deferred);
+            deferred.remove(id);
+//        std::cerr << "after id " << id << ": deferred now contains " << deferred.size() << " element(s)" << std::endl;
+        }
+    }
+}
+
+void
+Grapher::markClosedChangesetsFrom(QString id, QSet<QString> &deferred)
+{
+    // A changeset should be marked as closed (i) if it is in the list
+    // of closed heads [and has no children]; or (ii) all of its
+    // children that have the same branch name as it are marked as
+    // closed [and there is at least one of those]
+
+    if (!m_changesets.contains(id)) {
+//        std::cerr << "no good" << std::endl;
+        return;
+    }
+
+//    std::cerr << "looking at id " << id << std::endl;
+            
+    Changeset *cs = m_changesets[id];
+    QString branch = cs->branch();
+            
+    bool closed = false;
+
+    if (m_closedIds.contains(id)) {
+
+        closed = true;
+
+    } else {
+
+        closed = false;
+        foreach (QString childId, cs->children()) {
+            if (!m_changesets.contains(childId)) {
+                continue;
+            }
+            Changeset *ccs = m_changesets[childId];
+            if (ccs->isOnBranch(branch)) {
+                if (ccs->closed()) {
+                    // closed becomes true only when we see a closed
+                    // child on the same branch
+                    closed = true;
+                } else {
+                    // and it becomes false as soon as we see any
+                    // un-closed child on the same branch
+                    closed = false;
+                    break;
+                }
+            }
+        }
+    }
+
+    if (closed) {
+        // set closed on this cset and its direct simple parents
+        QString csid = id;
+        while (cs) {
+            cs->setClosed(true);
+            if (cs->parents().size() == 1) {
+                QString pid = cs->parents()[0];
+                if (!m_changesets.contains(pid)) break;
+                cs = m_changesets[pid];
+                if (cs->children().size() > 1) {
+//                    std::cerr << "adding pid " << pid << " (it has more than one child)" << std::endl;
+                    deferred.insert(pid); // examine later
+                    cs = 0;
+                }
+            } else if (cs->parents().size() > 1) {
+                foreach (QString pid, cs->parents()) {
+//                    std::cerr << "recursing to pid " << pid << " (it is one of multiple parents)" << std::endl;
+                    markClosedChangesetsFrom(pid, deferred);
+                }
+                cs = 0;
+            } else {
+                cs = 0;
+            }
+        }
+    } else {
+        cs->setClosed(false);
+    }
+    
+//    std::cerr << "finished with id " << id << std::endl;
+}
+
+void Grapher::layout(Changesets csets,
+                     QStringList uncommittedParents,
+                     QString uncommittedBranch)
+{
+    m_changesets.clear();
+    m_items.clear();
+    m_alloc.clear();
+    m_branchHomes.clear();
+
+    m_uncommittedParents = uncommittedParents;
+    m_haveAllocatedUncommittedColumn = false;
+    m_uncommittedParentRow = 0;
+    m_uncommitted = 0;
+
+    DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl;
+
+    if (csets.empty()) return;
+
+    // Initialise changesets hash
+
+    foreach (Changeset *cs, csets) {
+
+        QString id = cs->id();
+
+        if (id == "") {
+            throw LayoutException("Changeset has no ID");
+        }
+        if (m_changesets.contains(id)) {
+            DEBUG << "Duplicate changeset ID " << id
+                  << " in Grapher::layout()" << endl;
+            throw LayoutException(QString("Duplicate changeset ID %1").arg(id));
+        }
+
+        m_changesets[id] = cs;
+    }
+    
+    // Set the children for each changeset
+
+    foreach (Changeset *cs, csets) {
+        QString id = cs->id();
+        foreach (QString parentId, cs->parents()) {
+            if (!m_changesets.contains(parentId)) continue;
+            Changeset *parent = m_changesets[parentId];
+            parent->addChild(id);
+        }
+    }
+    
+    // Ensure the closed branch changesets are all marked as closed.
+
+    markClosedChangesets();
+
+    // Create (but don't yet position) the changeset items
+
+    foreach (Changeset *cs, csets) {
+        if (cs->closed() && !m_showClosedBranches) continue;
+        QString id = cs->id();
+        ChangesetItem *item = new ChangesetItem(cs);
+        item->setX(0);
+        item->setY(0);
+        item->setZValue(0);
+        m_items[id] = item;
+        m_scene->addChangesetItem(item);
+    }
+    
+    // Ensure the closing changeset items are appropriately marked
+
+    foreach (QString closedId, m_closedIds) {
+        if (!m_items.contains(closedId)) continue;
+        m_items[closedId]->setClosingCommit(true);
+    }
+
+    // Add the connecting lines
+
+    foreach (Changeset *cs, csets) {
+        QString id = cs->id();
+        if (!m_items.contains(id)) continue;
+        ChangesetItem *item = m_items[id];
+        bool merge = (cs->parents().size() > 1);
+        foreach (QString parentId, cs->parents()) {
+            if (!m_changesets.contains(parentId)) continue;
+            ConnectionItem *conn = new ConnectionItem();
+            if (merge) conn->setConnectionType(ConnectionItem::Merge);
+            conn->setChild(item);
+            conn->setZValue(-1);
+            if (m_items.contains(parentId)) {
+                conn->setParent(m_items[parentId]);
+            } else {
+                conn->setMergedBranch(m_changesets[parentId]->branch());
+            }
+            m_scene->addItem(conn);
+        }
+    }
+
+    // Add uncommitted item and connecting line as necessary
+
+    if (!m_uncommittedParents.empty()) {
+
+        m_uncommitted = new UncommittedItem();
+        m_uncommitted->setBranch(uncommittedBranch);
+        m_uncommitted->setZValue(10);
+        m_scene->addUncommittedItem(m_uncommitted);
+
+        bool haveParentOnBranch = false;
+        foreach (QString p, m_uncommittedParents) {
+            if (!m_items.contains(p)) continue;
+            ConnectionItem *conn = new ConnectionItem();
+            conn->setConnectionType(ConnectionItem::Merge);
+            ChangesetItem *pitem = m_items[p];
+            conn->setParent(pitem);
+            conn->setChild(m_uncommitted);
+            conn->setZValue(-1);
+            m_scene->addItem(conn);
+            if (pitem) {
+                if (pitem->getChangeset()->isOnBranch(uncommittedBranch)) {
+                    haveParentOnBranch = true;
+                }
+            }
+        }
+
+        // If the uncommitted item has no parents on the same branch,
+        // tell it it has a new branch (the "show branch" flag is set
+        // elsewhere for this item)
+        m_uncommitted->setIsNewBranch(!haveParentOnBranch);
+
+        // Uncommitted is a merge if it has more than one parent
+        m_uncommitted->setIsMerge(m_uncommittedParents.size() > 1);
+    }
+
+    // Add the branch labels
+
+    foreach (Changeset *cs, csets) {
+        QString id = cs->id();
+        if (!m_items.contains(id)) continue;
+        ChangesetItem *item = m_items[id];
+        bool haveChildOnSameBranch = false;
+        foreach (QString childId, cs->children()) {
+            Changeset *child = m_changesets[childId];
+            if (child->branch() == cs->branch()) {
+                haveChildOnSameBranch = true;
+                break;
+            }
+        }
+        item->setShowBranch(!haveChildOnSameBranch);
+    }
+
+    // We need to lay out the changesets in forward chronological
+    // order.  We have no guarantees about the order in which
+    // changesets appear in the list -- in a simple repository they
+    // will generally be reverse chronological, but that's far from
+    // guaranteed.  So, sort explicitly using the date comparator
+    // above
+
+    qStableSort(csets.begin(), csets.end(), compareChangesetsByDate);
+
+    foreach (Changeset *cs, csets) {
+        DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp()
+              << ", date " << cs->datetime().toStdString() << endl;
+    }
+
+    m_handled.clear();
+    foreach (Changeset *cs, csets) {
+        layoutRow(cs->id());
+    }
+
+    allocateBranchHomes(csets);
+
+    m_handled.clear();
+    foreach (Changeset *cs, csets) {
+        foreach (QString parentId, cs->parents()) {
+            if (!m_handled.contains(parentId) &&
+                m_changesets.contains(parentId)) {
+                layoutCol(parentId);
+            }
+        }
+        layoutCol(cs->id());
+    }
+
+    // Find row and column extents.  We know that 0 is an upper bound
+    // on row, and that mincol must be <= 0 and maxcol >= 0, so these
+    // initial values are good
+
+    int minrow = 0, maxrow = 0;
+    int mincol = 0, maxcol = 0;
+
+    foreach (int r, m_alloc.keys()) {
+        if (r < minrow) minrow = r;
+        if (r > maxrow) maxrow = r;
+        ColumnSet &c = m_alloc[r];
+        foreach (int i, c) {
+            if (i < mincol) mincol = i;
+            if (i > maxcol) maxcol = i;
+        }
+    }
+
+    int datemincol = mincol, datemaxcol = maxcol;
+
+    if (mincol == maxcol) {
+        --datemincol;
+        ++datemaxcol;
+    } else if (m_alloc[minrow].contains(mincol)) {
+        --datemincol;
+    }
+
+    // We've given the uncommitted item a column, but not a row yet --
+    // it always goes at the top
+
+    if (m_uncommitted) {
+        --minrow;
+        DEBUG << "putting uncommitted item at row " << minrow << endl;
+        m_uncommitted->setRow(minrow);
+    }
+
+    // Changeset items that have nothing to either side of them can be
+    // made double-width
+
+    foreach (Changeset *cs, csets) {
+        QString id = cs->id();
+        if (!m_items.contains(id)) continue;
+        ChangesetItem *item = m_items[id];
+        if (isAvailable(item->row(), item->column()-1) &&
+            isAvailable(item->row(), item->column()+1)) {
+            item->setWide(true);
+        }
+    }
+
+    if (m_uncommitted) {
+        if (isAvailable(m_uncommitted->row(), m_uncommitted->column()-1) &&
+            isAvailable(m_uncommitted->row(), m_uncommitted->column()+1)) {
+            m_uncommitted->setWide(true);
+        }
+    }
+
+    QString prevDate;
+    int changeRow = 0;
+
+    bool even = false;
+    int n = 0;
+
+    for (int row = minrow; row <= maxrow; ++row) {
+
+        QString date = m_rowDates[row];
+        n++;
+
+        if (date != prevDate) {
+            if (prevDate != "") {
+                m_scene->addDateRange(prevDate, changeRow, n, even);
+                even = !even;
+            }
+            prevDate = date;
+            changeRow = row;
+            n = 0;
+        }
+    }
+    
+    if (n > 0) {
+        m_scene->addDateRange(prevDate, changeRow, n+1, even);
+        even = !even;
+    }
+
+    m_scene->itemAddCompleted();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/grapher.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,105 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 GRAPHER_H
+#define GRAPHER_H
+
+#include "changeset.h"
+#include "changesetitem.h"
+#include "uncommitteditem.h"
+#include "changesetscene.h"
+
+#include <QSet>
+#include <QMap>
+#include <QPair>
+
+#include <exception>
+
+class Grapher
+{
+public:
+    Grapher(ChangesetScene *scene);
+
+    void layout(Changesets csets,
+                QStringList uncommittedParents,
+                QString uncommittedBranch);
+
+    ChangesetItem *getItemFor(Changeset *cs);
+    ChangesetItem *getItemFor(QString id);
+
+    UncommittedItem *getUncommittedItem() { return m_uncommitted; }
+
+    void setClosedHeadIds(QSet<QString> closed) { m_closedIds = closed; }
+
+    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:
+    ChangesetScene *m_scene;
+
+    typedef QMap<QString, Changeset *> IdChangesetMap;
+    IdChangesetMap m_changesets;
+
+    typedef QMap<QString, ChangesetItem *> IdItemMap;
+    IdItemMap m_items;
+
+    typedef QSet<int> ColumnSet;
+    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;
+
+    typedef QMap<int, QString> RowDateMap;
+    RowDateMap m_rowDates;
+
+    QSet<QString> m_closedIds;
+
+    bool m_showDates;
+    bool m_showClosedBranches;
+
+    QStringList m_uncommittedParents;
+    int m_uncommittedParentRow;
+    UncommittedItem *m_uncommitted;
+    bool m_haveAllocatedUncommittedColumn;
+
+    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);
+    bool isAvailable(int row, int col);
+    void markClosedChangesets();
+    void markClosedChangesetsFrom(QString id, QSet<QString> &deferred);
+};
+
+#endif 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hgaction.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,138 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 HGACTION_H
+#define HGACTION_H
+
+#include <QString>
+#include <QStringList>
+
+enum HGACTIONS
+{
+    ACT_NONE,
+    ACT_TEST_HG,
+    ACT_TEST_HG_EXT,
+    ACT_QUERY_PATHS,
+    ACT_QUERY_BRANCH,
+    ACT_STAT,
+    ACT_RESOLVE_LIST,
+    ACT_QUERY_HEADS,
+    ACT_QUERY_HEADS_ACTIVE,
+    ACT_QUERY_PARENTS,
+    ACT_LOG,
+    ACT_LOG_INCREMENTAL,
+    ACT_REMOVE,
+    ACT_ADD,
+    ACT_INCOMING,
+    ACT_PUSH,
+    ACT_PULL,
+    ACT_CLONEFROMREMOTE,
+    ACT_INIT,
+    ACT_COMMIT,
+    ACT_CLOSE_BRANCH,
+    ACT_ANNOTATE,
+    ACT_UNCOMMITTED_SUMMARY,
+    ACT_DIFF_SUMMARY,
+    ACT_FOLDERDIFF,
+    ACT_CHGSETDIFF,
+    ACT_UPDATE,
+    ACT_REVERT,
+    ACT_MERGE,
+    ACT_SERVE,
+    ACT_RESOLVE_MARK,
+    ACT_RETRY_MERGE,
+    ACT_TAG,
+    ACT_NEW_BRANCH,
+    ACT_HG_IGNORE,
+    ACT_COPY_FILE,
+    ACT_RENAME_FILE
+};
+
+struct HgAction
+{
+    HGACTIONS action;
+    QString workingDir;
+    QStringList params;
+    QString executable; // empty for normal Hg, but gets filled in by hgrunner
+    void *extraData;
+
+    HgAction() : action(ACT_NONE), extraData(0) { }
+
+    HgAction(HGACTIONS _action, QString _wd, QStringList _params) :
+        action(_action), workingDir(_wd), params(_params), extraData(0) { }
+
+    HgAction(HGACTIONS _action, QString _wd, QStringList _params, void *_d) :
+        action(_action), workingDir(_wd), params(_params), extraData(_d) { }
+
+    bool operator==(const HgAction &a) {
+        bool equal = (a.action == action && a.workingDir == workingDir &&
+                      a.params == params && a.executable == executable &&
+                      a.extraData == extraData);
+        return equal;
+    }
+
+    bool shouldBeFast() const {
+        switch (action) {
+        case ACT_NONE:
+        case ACT_TEST_HG:
+        case ACT_TEST_HG_EXT:
+        case ACT_QUERY_PATHS:
+        case ACT_QUERY_BRANCH:
+        case ACT_STAT:
+        case ACT_RESOLVE_LIST:
+        case ACT_QUERY_HEADS:
+        case ACT_QUERY_HEADS_ACTIVE:
+        case ACT_QUERY_PARENTS:
+        case ACT_LOG_INCREMENTAL:
+            return true;
+        default:
+            return false;
+        }
+    }
+    
+    bool mayBeInteractive() const {
+	switch (action) {
+        case ACT_TEST_HG_EXT: // so we force the module load to be tested
+	case ACT_INCOMING:
+	case ACT_PUSH:
+	case ACT_PULL:
+	case ACT_CLONEFROMREMOTE:
+	case ACT_FOLDERDIFF:
+	case ACT_CHGSETDIFF:
+	case ACT_SERVE:
+	    return true;
+	default:
+	    return false;
+	}
+    }
+
+    bool makesSenseToCancel() const {
+        switch (action) {
+        case ACT_INCOMING:
+        case ACT_PUSH:
+        case ACT_PULL:
+        case ACT_CLONEFROMREMOTE:
+        case ACT_FOLDERDIFF:
+        case ACT_CHGSETDIFF:
+            return true;
+        default:
+            return false;
+        }
+    }
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hgignoredialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,155 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 "hgignoredialog.h"
+#include "common.h"
+#include "debug.h"
+
+#include <QGridLayout>
+#include <QRadioButton>
+#include <QLabel>
+#include <QDialogButtonBox>
+#include <QPushButton>
+
+HgIgnoreDialog::HgIgnoreDialog(QWidget *parent,
+			       QString title,
+			       QString introText,
+			       QString question,
+			       QStringList options,
+			       QString okButtonText) :
+    QDialog(parent)
+{
+    setWindowTitle(title);
+
+    QGridLayout *layout = new QGridLayout;
+    setLayout(layout);
+    
+    int row = 0;
+
+    QLabel *label = new QLabel(QString("%1%2").arg(introText).arg(question));
+    label->setWordWrap(true);
+    layout->addWidget(label, row++, 0, 1, 2);
+
+    if (!options.empty()) {
+	layout->addWidget(new QLabel("  "), row, 0);
+	layout->setColumnStretch(1, 10);
+	bool first = true;
+	foreach (QString option, options) {
+	    QRadioButton *b = new QRadioButton(option);
+	    layout->addWidget(b, row++, 1);
+	    if (first) {
+		m_option = option;
+		b->setChecked(true);
+		first = false;
+	    }
+	    connect(b, SIGNAL(toggled(bool)), this, SLOT(optionToggled(bool)));
+	}
+    }
+
+    QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok |
+                                                  QDialogButtonBox::Cancel);
+    layout->addWidget(bbox, row++, 0, 1, 2);
+    bbox->button(QDialogButtonBox::Ok)->setDefault(true);
+    bbox->button(QDialogButtonBox::Ok)->setText(okButtonText);
+    bbox->button(QDialogButtonBox::Cancel)->setAutoDefault(false);
+
+    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
+    connect(bbox, SIGNAL(rejected()), this, SLOT(reject()));
+}
+
+void
+HgIgnoreDialog::optionToggled(bool checked)
+{
+    QObject *s = sender();
+    QRadioButton *rb = qobject_cast<QRadioButton *>(s);
+    if (rb && checked) {
+	m_option = rb->text();
+    }
+}
+
+HgIgnoreDialog::IgnoreType
+HgIgnoreDialog::confirmIgnore(QWidget *parent,
+			      QStringList files,
+                              QStringList suffixes,
+                              QString directory)
+{
+    QString intro = "<qt><h3>";
+    intro += tr("Ignore files");
+    intro += "</h3><p>";
+
+    if (files.size() < 10) {
+        intro += tr("You have asked to ignore the following files:</p><p>");
+        intro += "<code>&nbsp;&nbsp;&nbsp;"
+	    + files.join("<br>&nbsp;&nbsp;&nbsp;") + "</code>";
+    } else {
+        intro += tr("You have asked to ignore %n file(s).", "", files.size());
+    }
+
+    intro += "</p></qt>";
+
+    QString textTheseFiles;
+    QString textTheseNames;
+    if (files.size() > 1) {
+        textTheseFiles = tr("Ignore these files only");
+        textTheseNames = tr("Ignore files with these names, in any folder");
+    } else {
+        textTheseFiles = tr("Ignore this file only");
+        textTheseNames = tr("Ignore files with the same name as this, in any folder");
+    }        
+
+    QString textThisFolder;
+    QString textTheseSuffixes;
+
+    QStringList options;
+    options << textTheseFiles;
+    options << textTheseNames;
+
+    if (directory != "") {
+        textThisFolder = tr("Ignore the whole folder \"%1\"")
+            .arg(directory);
+        options << textThisFolder;
+    }
+
+    if (suffixes.size() > 1) {
+
+        textTheseSuffixes = tr("Ignore all files with these extensions:\n%1")
+            .arg(suffixes.join(", "));
+        options << textTheseSuffixes;
+
+    } else if (suffixes.size() > 0) {
+
+        textTheseSuffixes = tr("Ignore all files with the extension \"%1\"")
+            .arg(suffixes[0]);
+        options << textTheseSuffixes;
+    }
+
+    HgIgnoreDialog d(parent, tr("Ignore files"),
+                     intro, tr("<p>Please choose whether to:</p>"),
+                     options, tr("Ignore"));
+
+    if (d.exec() == QDialog::Accepted) {
+        QString option = d.getOption();
+        DEBUG << "HgIgnoreDialog::confirmIgnore: option = " << option << endl;
+        if (option == textTheseFiles) return IgnoreGivenFilesOnly;
+        else if (option == textTheseNames) return IgnoreAllFilesOfGivenNames;
+        else if (option == textTheseSuffixes) return IgnoreAllFilesOfGivenSuffixes;
+        else if (option == textThisFolder) return IgnoreWholeDirectory;
+    }
+
+    return IgnoreNothing;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hgignoredialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,57 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 _HGIGNORE_DIALOG_H_
+#define _HGIGNORE_DIALOG_H_
+
+#include <QDialog>
+
+class HgIgnoreDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    enum IgnoreType {
+	IgnoreNothing,
+	IgnoreGivenFilesOnly,
+	IgnoreAllFilesOfGivenNames,
+	IgnoreAllFilesOfGivenSuffixes,
+        IgnoreWholeDirectory
+    };
+
+    static IgnoreType confirmIgnore(QWidget *parent,
+				    QStringList files,
+                                    QStringList suffixes,
+                                    QString directory);
+
+private slots:
+    void optionToggled(bool);
+
+private:
+    HgIgnoreDialog(QWidget *parent,
+		   QString title,
+		   QString introText,
+		   QString question,
+		   QStringList options,
+		   QString okButtonText);
+
+    QString getOption() const { return m_option; }
+
+    QString m_option;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hgrunner.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,689 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "hgrunner.h"
+#include "common.h"
+#include "debug.h"
+#include "settingsdialog.h"
+
+#include <QSettings>
+#include <QInputDialog>
+#include <QStandardPaths>
+#include <QTemporaryFile>
+#include <QDir>
+#include <QProgressBar>
+#include <QPushButton>
+#include <QGridLayout>
+
+#include <iostream>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef Q_OS_WIN32
+#include <unistd.h>
+#include <termios.h>
+#include <fcntl.h>
+#else
+#include <process.h>
+#endif
+
+HgRunner::HgRunner(QString myDirPath, QWidget *parent) :
+    QWidget(parent),
+    m_myDirPath(myDirPath)
+{
+    QGridLayout *layout = new QGridLayout(this);
+    layout->setMargin(0);
+
+    m_progress = new QProgressBar;
+    layout->addWidget(m_progress, 0, 0);
+
+    m_cancel = new QPushButton;
+    m_cancel->setIcon(QIcon(":images/cancel-small.png"));
+    m_cancel->setFlat(true);
+    m_cancel->setFixedHeight(m_progress->sizeHint().height());
+    m_cancel->setFixedWidth(m_progress->sizeHint().height());
+    connect(m_cancel, SIGNAL(clicked()), this, SLOT(killCurrentActions()));
+    layout->addWidget(m_cancel, 0, 1);
+
+    m_proc = 0;
+
+    // Always unbundle the extension: even if it already exists (in
+    // case we're upgrading) and even if we're not going to use it (so
+    // that it's available in case someone wants to use it later,
+    // e.g. to fix a malfunctioning setup).  But the path we actually
+    // prefer is the one in the settings first, if it exists; then the
+    // unbundled one; then anything in the path if for some reason
+    // unbundling failed
+    unbundleExtension();
+
+    m_progress->setTextVisible(false);
+    hide();
+    m_isRunning = false;
+}
+
+HgRunner::~HgRunner()
+{
+    closeTerminal();
+    if (m_proc) {
+        m_proc->kill();
+        m_proc->deleteLater();
+    }
+    if (m_authFilePath != "") {
+        QFile(m_authFilePath).remove();
+    }
+    //!!! and remove any other misc auth file paths...
+}
+
+QString HgRunner::getUnbundledFileName()
+{
+    return SettingsDialog::getUnbundledExtensionFileName();
+}
+
+QString HgRunner::unbundleExtension()
+{
+    // Pull out the bundled Python file into a temporary file, and
+    // copy it to our known extension location, replacing the magic
+    // text NO_EASYHG_IMPORT_PATH with our installation location
+
+    QString bundled = ":easyhg.py";
+    QString unbundled = getUnbundledFileName();
+
+    QString target = QFileInfo(unbundled).path();
+    if (!QDir().mkpath(target)) {
+        DEBUG << "Failed to make unbundle path " << target << endl;
+        std::cerr << "Failed to make unbundle path " << target << std::endl;
+        return ""; 
+    }
+
+    QFile bf(bundled);
+    DEBUG << "unbundle: bundled file will be " << bundled << endl;
+    if (!bf.exists() || !bf.open(QIODevice::ReadOnly)) {
+        DEBUG << "Bundled extension is missing!" << endl;
+        return "";
+    }
+
+    QTemporaryFile tmpfile(QString("%1/easyhg.py.XXXXXX").arg(target));
+    tmpfile.setAutoRemove(false);
+    DEBUG << "unbundle: temp file will be " << tmpfile.fileName() << endl;
+    if (!tmpfile.open()) {
+        DEBUG << "Failed to open temporary file " << tmpfile.fileName() << endl;
+        std::cerr << "Failed to open temporary file " << tmpfile.fileName() << std::endl;
+        return "";
+    }
+
+    QString all = QString::fromUtf8(bf.readAll());
+    all.replace("NO_EASYHG_IMPORT_PATH", m_myDirPath);
+    tmpfile.write(all.toUtf8());
+    DEBUG << "unbundle: wrote " << all.length() << " characters" << endl;
+
+    tmpfile.close();
+
+    QFile ef(unbundled);
+    if (ef.exists()) {
+        DEBUG << "unbundle: removing old file " << unbundled << endl;
+        ef.remove();
+    }
+    DEBUG << "unbundle: renaming " << tmpfile.fileName() << " to " << unbundled << endl;
+    if (!tmpfile.rename(unbundled)) {
+        DEBUG << "Failed to move temporary file to target file " << unbundled << endl;
+        std::cerr << "Failed to move temporary file to target file " << unbundled << std::endl;
+        return "";
+    }
+    
+    DEBUG << "Unbundled extension to " << unbundled << endl;
+    return unbundled;
+}        
+
+void HgRunner::requestAction(HgAction action)
+{
+    DEBUG << "requestAction " << action.action << ": " << m_queue.size() << " thing(s) in queue, current action is " << m_currentAction.action << endl;
+    bool pushIt = true;
+
+    action = expandEnvironment(action);
+
+    if (m_queue.empty()) {
+        if (action == m_currentAction) {
+            // this request is identical to the thing we're executing
+            DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl;
+            pushIt = false;
+        }
+    } else {
+        HgAction last = m_queue.back();
+        if (action == last) {
+            // this request is identical to the previous thing we
+            // queued which we haven't executed yet
+            DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl;
+            pushIt = false;
+        }
+    }
+    if (pushIt) {
+        m_queue.push_back(action);
+    }
+    checkQueue();
+}
+
+HgAction HgRunner::expandEnvironment(HgAction action)
+{
+    // Adjust the executable and params for action to match our actual
+    // environment. We do this when the action is received, rather
+    // than when we execute it, so that we can compare
+    // (post-expansion) commands to see e.g. whether the one just
+    // received is the same as the one we're currently executing
+
+    QString executable = action.executable;
+    QStringList params = action.params;
+
+    if (executable == "") {
+        // This is a Hg command
+        executable = getHgBinaryName();
+        if (executable == "") executable = "hg";
+
+        QString ssh = getSshBinaryName();
+        if (ssh != "") {
+            params.push_front(QString("ui.ssh=\"%1\"").arg(ssh));
+            params.push_front("--config");
+        }
+
+        if (action.mayBeInteractive()) {
+            params.push_front("ui.interactive=true");
+            params.push_front("--config");
+            QSettings settings;
+            if (settings.value("useextension", true).toBool()) {
+                params = addExtensionOptions(params);
+            }
+        }            
+    }
+
+    action.executable = executable;
+    action.params = params;
+
+    return action;
+}
+
+QString HgRunner::getHgBinaryName()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    return settings.value("hgbinary", "").toString();
+}
+
+QString HgRunner::getSshBinaryName()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    return settings.value("sshbinary", "").toString();
+}
+
+QString HgRunner::getExtensionLocation()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    QString extpath = settings.value("extensionpath", "").toString();
+    if (extpath != "" && QFile(extpath).exists()) return extpath;
+    return "";
+}   
+
+void HgRunner::started()
+{
+    DEBUG << "started" << endl;
+    /*
+    m_proc->write("blah\n");
+    m_proc->write("blah\n");
+    m_proc -> closeWriteChannel();
+    */
+}
+
+void HgRunner::noteUsername(QString name)
+{
+    m_userName = name;
+}
+
+void HgRunner::noteRealm(QString realm)
+{
+    m_realm = realm;
+}
+
+void HgRunner::getUsername()
+{
+    if (m_ptyFile) {
+        bool ok = false;
+        QString prompt = tr("User name:");
+        if (m_realm != "") {
+            prompt = tr("User name for \"%1\":").arg(m_realm);
+        }
+        QString pwd = QInputDialog::getText
+            (qobject_cast<QWidget *>(parent()),
+            tr("Enter user name"), prompt,
+            QLineEdit::Normal, QString(), &ok);
+        if (ok) {
+            m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
+            m_ptyFile->flush();
+            return;
+        } else {
+            DEBUG << "HgRunner::getUsername: user cancelled" << endl;
+            killCurrentCommand();
+            return;
+        }
+    }
+    // user cancelled or something went wrong
+    DEBUG << "HgRunner::getUsername: something went wrong" << endl;
+    killCurrentCommand();
+}
+
+void HgRunner::getPassword()
+{
+    if (m_ptyFile) {
+        bool ok = false;
+        QString prompt = tr("Password:");
+        if (m_userName != "") {
+            if (m_realm != "") {
+                prompt = tr("Password for \"%1\" at \"%2\":")
+                         .arg(m_userName).arg(m_realm);
+            } else {
+                prompt = tr("Password for user \"%1\":")
+                         .arg(m_userName);
+            }
+        }
+        QString pwd = QInputDialog::getText
+            (qobject_cast<QWidget *>(parent()),
+            tr("Enter password"), prompt,
+             QLineEdit::Password, QString(), &ok);
+        if (ok) {
+            m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
+            m_ptyFile->flush();
+            return;
+        } else {
+            DEBUG << "HgRunner::getPassword: user cancelled" << endl;
+            killCurrentCommand();
+            return;
+        }
+    }
+    // user cancelled or something went wrong
+    DEBUG << "HgRunner::getPassword: something went wrong" << endl;
+    killCurrentCommand();
+}
+
+bool HgRunner::checkPrompts(QString chunk)
+{
+    //DEBUG << "checkPrompts: " << chunk << endl;
+
+    if (!m_currentAction.mayBeInteractive()) return false;
+
+    QString text = chunk.trimmed();
+    QString lower = text.toLower();
+    if (lower.endsWith("password:")) {
+        getPassword();
+        return true;
+    }
+    if (lower.endsWith("user:") || lower.endsWith("username:")) {
+        getUsername();
+        return true;
+    }
+    QRegExp userRe("\\buser(name)?:\\s*([^\\s]+)");
+    if (userRe.indexIn(text) >= 0) {
+        noteUsername(userRe.cap(2));
+    }
+    QRegExp realmRe("\\brealmr:\\s*([^\\s]+)");
+    if (realmRe.indexIn(text) >= 0) {
+        noteRealm(realmRe.cap(1));
+    }
+    return false;
+}
+
+void HgRunner::dataReadyStdout()
+{
+    DEBUG << "dataReadyStdout" << endl;
+    if (!m_proc) return;
+    QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput());
+    if (!checkPrompts(chunk)) {
+        m_stdout += chunk;
+    }
+}
+
+void HgRunner::dataReadyStderr()
+{
+    DEBUG << "dataReadyStderr" << endl;
+    if (!m_proc) return;
+    QString chunk = QString::fromUtf8(m_proc->readAllStandardError());
+    DEBUG << chunk;
+    if (!checkPrompts(chunk)) {
+        m_stderr += chunk;
+    }
+}
+
+void HgRunner::dataReadyPty()
+{
+    DEBUG << "dataReadyPty" << endl;
+    QString chunk = QString::fromUtf8(m_ptyFile->readAll());
+    DEBUG << "chunk of " << chunk.length() << " chars" << endl;
+    if (!checkPrompts(chunk)) {
+        m_stdout += chunk;
+    }
+}
+
+void HgRunner::error(QProcess::ProcessError)
+{
+    finished(-1, QProcess::CrashExit);
+}
+
+void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
+{
+    if (!m_proc) return;
+
+    // Save the current action and reset m_currentAction before we
+    // emit a signal to mark the completion; otherwise we may be
+    // resetting the action after a slot has already tried to set it
+    // to something else to start a new action
+
+    HgAction completedAction = m_currentAction;
+
+    DEBUG << "HgRunner::finished: completed " << completedAction.action << endl;
+
+    m_isRunning = false;
+    m_currentAction = HgAction();
+
+    //closeProcInput();
+    m_proc->deleteLater();
+    m_proc = 0;
+
+    if (completedAction.action == ACT_NONE) {
+        DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl;
+    } else {
+        if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
+            DEBUG << "HgRunner::finished: Command completed successfully"
+                  << endl;
+//            DEBUG << "stdout is " << m_stdout << endl;
+            emit commandCompleted(completedAction, m_stdout);
+        } else {
+            DEBUG << "HgRunner::finished: Command failed, exit code "
+                  << procExitCode << ", exit status " << int(procExitStatus)
+                  << ", stderr follows" << endl;
+            DEBUG << m_stderr << endl;
+            emit commandFailed(completedAction, m_stderr, m_stdout);
+        }
+    }
+
+    checkQueue();
+}
+
+void HgRunner::killCurrentActions()
+{
+    HgAction current = m_currentAction;
+    m_queue.clear();
+    killCurrentCommand();
+    emit commandCancelled(current);
+}
+
+void HgRunner::killCurrentCommand()
+{
+    if (m_isRunning) {
+        m_currentAction.action = ACT_NONE; // so that we don't bother to notify
+        if (m_proc) m_proc->kill();
+    }
+}
+
+void HgRunner::checkQueue()
+{
+    if (m_isRunning) {
+        return;
+    }
+    if (m_queue.empty()) {
+        hide();
+        return;
+    }
+    HgAction toRun = m_queue.front();
+    m_queue.pop_front();
+    DEBUG << "checkQueue: have action: running " << toRun.action << endl;
+    startCommand(toRun);
+}
+
+void HgRunner::pruneOldAuthFiles()
+{
+    QString path = QStandardPaths::writableLocation
+        (QStandardPaths::CacheLocation);
+    QDir d(path);
+    if (!d.exists()) return;
+    QStringList filters;
+    filters << "easyhg.*.dat";
+    QStringList fl = d.entryList(filters);
+    foreach (QString f, fl) {
+        QStringList parts = f.split('.');
+        if (parts.size() > 1) {
+            int pid = parts[1].toInt();
+            DEBUG << "Checking pid " << pid << " for cache file " << f << endl;
+
+            ProcessStatus ps = GetProcessStatus(pid);
+            if (ps == ProcessNotRunning) {
+                DEBUG << "Removing stale cache file " << f << endl;
+                QDir(d).remove(f);
+            }
+        }
+    }
+}
+
+QString HgRunner::getAuthFilePath()
+{
+    if (m_authFilePath == "") {
+
+        pruneOldAuthFiles();
+
+        QByteArray fileExt = randomKey();
+        if (fileExt == QByteArray()) {
+            DEBUG << "HgRunner::getAuthFilePath: Failed to get proper auth file ext" << endl;
+            return "";
+        }
+        QString fileExt16 = QString::fromLocal8Bit(fileExt.toBase64()).left(16)
+            .replace('+', '-').replace('/', '_');
+        QString path = QStandardPaths::writableLocation
+            (QStandardPaths::CacheLocation);
+        QDir().mkpath(path);
+        if (path == "") {
+            DEBUG << "HgRunner::getAuthFilePath: Failed to get cache location" << endl;
+            return "";
+        }
+
+        m_authFilePath = QString("%1/easyhg.%2.%3.dat").arg(path)
+            .arg(getpid()).arg(fileExt16);
+    }
+
+    return m_authFilePath;
+}
+
+QString HgRunner::getAuthKey()
+{
+    if (m_authKey == "") {
+        QByteArray key = randomKey();
+        if (key == QByteArray()) {
+            DEBUG << "HgRunner::getAuthKey: Failed to get proper auth key" << endl;
+            return "";
+        }
+        QString key16 = QString::fromLocal8Bit(key.toBase64()).left(16);
+        m_authKey = key16;
+    }
+
+    return m_authKey;
+}
+
+QStringList HgRunner::addExtensionOptions(QStringList params)
+{
+    QString extpath = getExtensionLocation();
+    if (extpath == "") {
+        DEBUG << "HgRunner::addExtensionOptions: Failed to get extension location" << endl;
+        return params;
+    }
+
+    QString afp = getAuthFilePath();
+    QString afk = getAuthKey();
+
+    if (afp != "" && afk != "") {
+        params.push_front(QString("easyhg.authkey=%1").arg(m_authKey));
+        params.push_front("--config");
+        params.push_front(QString("easyhg.authfile=%1").arg(m_authFilePath));
+        params.push_front("--config");
+    }
+
+    // Looks like this one must be without quotes, even though the SSH
+    // one above only works on Windows if it has quotes (at least where
+    // there is a space in the path).  Odd
+    params.push_front(QString("extensions.easyhg=%1").arg(extpath));
+    params.push_front("--config");
+
+    return params;
+}
+
+void HgRunner::startCommand(HgAction action)
+{
+    if (action.workingDir.isEmpty()) {
+        // We require a working directory, never just operate in pwd
+        emit commandFailed(action, "EasyMercurial: No working directory supplied, will not run Mercurial command without one", "");
+        return;
+    }
+
+    m_isRunning = true;
+    m_progress->setRange(0, 0);
+    if (!action.shouldBeFast()) {
+        show();
+        m_cancel->setVisible(action.makesSenseToCancel());
+    }
+    m_stdout.clear();
+    m_stderr.clear();
+    m_realm = "";
+    m_userName = "";
+
+    m_proc = new QProcess;
+
+    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+
+#ifdef Q_OS_WIN32
+    // On Win32 we like to bundle Hg and other executables with EasyHg
+    if (m_myDirPath != "") {
+        env.insert("PATH", m_myDirPath + ";" + env.value("PATH"));
+    }
+#endif
+
+#ifdef Q_OS_MAC
+    if (QSettings().value("python32", false).toBool()) {
+        env.insert("VERSIONER_PYTHON_PREFER_32_BIT", "1");
+    }
+#endif
+
+    env.insert("LANG", "en_US.utf8");
+    env.insert("LC_ALL", "en_US.utf8");
+    env.insert("HGENCODING", "utf8");
+    env.insert("HGPLAIN", "1");
+    m_proc->setProcessEnvironment(env);
+
+    connect(m_proc, SIGNAL(started()), this, SLOT(started()));
+    connect(m_proc, SIGNAL(error(QProcess::ProcessError)),
+            this, SLOT(error(QProcess::ProcessError)));
+    connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)),
+            this, SLOT(finished(int, QProcess::ExitStatus)));
+    connect(m_proc, SIGNAL(readyReadStandardOutput()),
+            this, SLOT(dataReadyStdout()));
+    connect(m_proc, SIGNAL(readyReadStandardError()),
+            this, SLOT(dataReadyStderr()));
+
+    m_proc->setWorkingDirectory(action.workingDir);
+
+    if (action.mayBeInteractive()) {
+        openTerminal();
+        if (m_ptySlaveFilename != "") {
+            DEBUG << "HgRunner: connecting to pseudoterminal" << endl;
+            m_proc->setStandardInputFile(m_ptySlaveFilename);
+//            m_proc->setStandardOutputFile(m_ptySlaveFilename);
+//            m_proc->setStandardErrorFile(m_ptySlaveFilename);
+        }
+    }
+
+    QString cmdline = action.executable;
+    foreach (QString param, action.params) cmdline += " " + param;
+    DEBUG << "HgRunner: starting: " << cmdline << " with cwd "
+          << action.workingDir << endl;
+
+    m_currentAction = action;
+
+    DEBUG << "set current action to " << m_currentAction.action << endl;
+    
+    emit commandStarting(action);
+
+    m_proc->start(action.executable, action.params);
+}
+
+void HgRunner::closeProcInput()
+{
+    DEBUG << "closeProcInput" << endl;
+
+    if (m_proc) m_proc->closeWriteChannel();
+}
+
+void HgRunner::openTerminal()
+{
+#ifndef Q_OS_WIN32
+    if (m_ptySlaveFilename != "") return; // already open
+    DEBUG << "HgRunner::openTerminal: trying to open new pty" << endl;
+    int master = posix_openpt(O_RDWR | O_NOCTTY);
+    if (master < 0) {
+        DEBUG << "openpt failed" << endl;
+        perror("openpt failed");
+        return;
+    }
+    struct termios t;
+    if (tcgetattr(master, &t)) {
+        DEBUG << "tcgetattr failed" << endl;
+        perror("tcgetattr failed");
+    }
+    cfmakeraw(&t);
+    if (tcsetattr(master, TCSANOW, &t)) {
+        DEBUG << "tcsetattr failed" << endl;
+        perror("tcsetattr failed");
+    }
+    if (grantpt(master)) {
+        perror("grantpt failed");
+    }
+    if (unlockpt(master)) {
+        perror("unlockpt failed");
+    }
+    char *slave = ptsname(master);
+    if (!slave) {
+        perror("ptsname failed");
+        ::close(master);
+        return;
+    }
+    m_ptyMasterFd = master;
+    m_ptyFile = new QFile();
+    connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty()));
+    if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) {
+        DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl;
+    }
+    m_ptySlaveFilename = slave;
+    DEBUG << "HgRunner::openTerminal: succeeded, slave is "
+          << m_ptySlaveFilename << endl;
+#endif
+}
+
+void HgRunner::closeTerminal()
+{
+#ifndef Q_OS_WIN32
+    if (m_ptySlaveFilename != "") {
+        delete m_ptyFile;
+        m_ptyFile = 0;
+        ::close(m_ptyMasterFd);
+        m_ptySlaveFilename = "";
+    }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hgrunner.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,120 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 HGRUNNER_H
+#define HGRUNNER_H
+
+#include "hgaction.h"
+
+#include <QWidget>
+#include <QProcess>
+
+class QProgressBar;
+class QPushButton;
+class QFile;
+
+#include <deque>
+
+class HgRunner : public QWidget
+{
+    Q_OBJECT
+
+public:
+    HgRunner(QString myDirPath, QWidget *parent = 0);
+    ~HgRunner();
+
+public slots:
+    void requestAction(HgAction action);
+    void killCurrentActions(); // kill anything running; clear the queue
+
+signals:
+    void commandStarting(HgAction action);
+    void commandCompleted(HgAction action, QString stdOut);
+    void commandFailed(HgAction action, QString stdErr, QString stdOut);
+
+    /**
+     * Emitted when the currently executing command is cancelled. Note
+     * that this clears the queue completely, so all subsequent
+     * commands are also discarded.
+     */
+    void commandCancelled(HgAction action);
+
+private slots:
+    void started();
+    void error(QProcess::ProcessError);
+    void finished(int procExitCode, QProcess::ExitStatus procExitStatus);
+    void dataReadyStdout();
+    void dataReadyStderr();
+    void dataReadyPty();
+
+private:
+    void checkQueue();
+    void startCommand(HgAction action);
+    void closeProcInput();
+    void killCurrentCommand();
+
+    void noteUsername(QString);
+    void noteRealm(QString);
+    void getUsername();
+    void getPassword();
+    bool checkPrompts(QString);
+
+    void openTerminal();
+    void closeTerminal();
+    
+    QString getHgBinaryName();
+    QString getSshBinaryName();
+    QString getExtensionLocation();
+
+    QString getAuthFilePath();
+    QString getAuthKey();
+    void pruneOldAuthFiles();
+
+    QString getUnbundledFileName();
+    QString unbundleExtension();
+
+    QProgressBar *m_progress;
+    QPushButton *m_cancel;
+
+    HgAction expandEnvironment(HgAction);
+    QStringList addExtensionOptions(QStringList);
+
+    int m_ptyMasterFd;
+    int m_ptySlaveFd;
+    QString m_ptySlaveFilename;
+    QFile *m_ptyFile;
+    
+    bool m_isRunning;
+    QProcess *m_proc;
+    QString m_stdout;
+    QString m_stderr;
+
+    QString m_userName;
+    QString m_realm;
+
+    QString m_myDirPath;
+    QString m_extensionPath;
+
+    QString m_authKey;
+    QString m_authFilePath;
+
+    typedef std::deque<HgAction> ActionQueue;
+    ActionQueue m_queue;
+    HgAction m_currentAction;
+};
+
+#endif // HGRUNNER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hgtabwidget.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,312 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "hgtabwidget.h"
+#include "common.h"
+#include "filestatuswidget.h"
+#include "historywidget.h"
+
+#include <QClipboard>
+#include <QContextMenuEvent>
+#include <QApplication>
+#include <QSettings>
+
+#include <iostream>
+
+HgTabWidget::HgTabWidget(QWidget *parent,
+                         QString workFolderPath) :
+    QTabWidget(parent),
+    m_haveMerge(false)
+{
+    // Work tab
+    m_fileStatusWidget = new FileStatusWidget;
+    m_fileStatusWidget->setLocalPath(workFolderPath);
+
+    connect(m_fileStatusWidget, SIGNAL(selectionChanged()),
+            this, SIGNAL(selectionChanged()));
+
+    connect(m_fileStatusWidget, SIGNAL(showAllChanged()),
+            this, SIGNAL(showAllChanged()));
+
+    connect(m_fileStatusWidget, SIGNAL(annotateFiles(QStringList)),
+            this, SIGNAL(annotateFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(diffFiles(QStringList)),
+            this, SIGNAL(diffFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(commitFiles(QStringList)),
+            this, SIGNAL(commitFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(revertFiles(QStringList)),
+            this, SIGNAL(revertFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(renameFiles(QStringList)),
+            this, SIGNAL(renameFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(copyFiles(QStringList)),
+            this, SIGNAL(copyFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(addFiles(QStringList)),
+            this, SIGNAL(addFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(removeFiles(QStringList)),
+            this, SIGNAL(removeFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(redoFileMerges(QStringList)),
+            this, SIGNAL(redoFileMerges(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(markFilesResolved(QStringList)),
+            this, SIGNAL(markFilesResolved(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(ignoreFiles(QStringList)),
+            this, SIGNAL(ignoreFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(unIgnoreFiles(QStringList)),
+            this, SIGNAL(unIgnoreFiles(QStringList)));
+
+    connect(m_fileStatusWidget, SIGNAL(showIn(QStringList)),
+            this, SIGNAL(showIn(QStringList)));
+
+    addTab(m_fileStatusWidget, tr("My work"));
+
+    // History graph tab
+    m_historyWidget = new HistoryWidget;
+    addTab(m_historyWidget, tr("History"));
+
+    connect(m_historyWidget, SIGNAL(commit()),
+            this, SIGNAL(commit()));
+    
+    connect(m_historyWidget, SIGNAL(revert()),
+            this, SIGNAL(revert()));
+    
+    connect(m_historyWidget, SIGNAL(showSummary()),
+            this, SIGNAL(showSummary()));
+    
+    connect(m_historyWidget, SIGNAL(newBranch()),
+            this, SIGNAL(newBranch()));
+    
+    connect(m_historyWidget, SIGNAL(noBranch()),
+            this, SIGNAL(noBranch()));
+    
+    connect(m_historyWidget, SIGNAL(diffWorkingFolder()),
+            this, SIGNAL(diffWorkingFolder()));
+
+    connect(m_historyWidget, SIGNAL(showWork()),
+            this, SLOT(showWorkTab()));
+
+    connect(m_historyWidget, SIGNAL(updateTo(QString)),
+            this, SIGNAL(updateTo(QString)));
+
+    connect(m_historyWidget, SIGNAL(diffToCurrent(QString)),
+            this, SIGNAL(diffToCurrent(QString)));
+
+    connect(m_historyWidget, SIGNAL(diffToParent(QString, QString)),
+            this, SIGNAL(diffToParent(QString, QString)));
+
+    connect(m_historyWidget, SIGNAL(showSummary(Changeset *)),
+            this, SIGNAL(showSummary(Changeset *)));
+
+    connect(m_historyWidget, SIGNAL(mergeFrom(QString)),
+            this, SIGNAL(mergeFrom(QString)));
+
+    connect(m_historyWidget, SIGNAL(newBranch(QString)),
+            this, SIGNAL(newBranch(QString)));
+
+    connect(m_historyWidget, SIGNAL(closeBranch(QString)),
+            this, SIGNAL(closeBranch(QString)));
+
+    connect(m_historyWidget, SIGNAL(tag(QString)),
+            this, SIGNAL(tag(QString)));
+}
+
+void HgTabWidget::clearSelections()
+{
+    m_fileStatusWidget->clearSelections();
+}
+
+void HgTabWidget::setCurrent(QStringList ids, QString branch)
+{
+    m_historyWidget->setCurrent(ids, branch, haveChangesToCommit());
+}
+
+void HgTabWidget::setClosedHeadIds(QSet<QString> closed)
+{
+    m_historyWidget->setClosedHeadIds(closed);
+}
+
+void HgTabWidget::clearAll()
+{
+    m_fileStatusWidget->clearWidgets();
+    m_historyWidget->clear();
+}
+
+void HgTabWidget::updateFileStates()
+{
+    m_fileStatusWidget->updateWidgets();
+}
+
+void HgTabWidget::updateHistory()
+{
+    m_historyWidget->update();
+}
+
+void HgTabWidget::setSearchText(QString text)
+{
+    m_fileStatusWidget->setSearchText(text);
+    m_historyWidget->setSearchText(text);
+}
+
+bool HgTabWidget::canDiff() const
+{
+    return canRevert();
+}
+
+bool HgTabWidget::canCommit() const
+{
+    if (!haveChangesToCommit()) return false;
+    if (!getAllUnresolvedFiles().empty()) return false;
+    return true;
+}
+
+bool HgTabWidget::canRevert() const
+{
+    // Not the same as canCommit() -- we can revert (and diff)
+    // unresolved files, but we can't commit them
+    if (!haveChangesToCommit() &&
+        getAllUnresolvedFiles().empty()) return false;
+    return true;
+}
+
+bool HgTabWidget::canAdd() const
+{
+    // Permit this only when work tab is visible
+    if (currentIndex() != 0) return false;
+
+    QStringList addable = getSelectedAddableFiles();
+    if (addable.empty()) return false;
+
+    QStringList removable = getSelectedRemovableFiles();
+    if (!removable.empty()) return false;
+
+    return true;
+}
+
+bool HgTabWidget::canRemove() const
+{
+    // Permit this only when work tab is visible
+    if (currentIndex() != 0) return false;
+
+    if (getSelectedRemovableFiles().empty()) return false;
+    if (!getSelectedAddableFiles().empty()) return false;
+    return true;
+}
+
+bool HgTabWidget::canResolve() const
+{
+    return !getAllUnresolvedFiles().empty();
+}
+
+bool HgTabWidget::canIgnore() const
+{
+    return canAdd();
+}
+
+bool HgTabWidget::haveChangesToCommit() const
+{
+    return m_haveMerge || m_fileStatusWidget->haveChangesToCommit();
+}
+
+QStringList HgTabWidget::getAllCommittableFiles() const
+{
+    return m_fileStatusWidget->getAllCommittableFiles();
+}
+
+QStringList HgTabWidget::getAllRevertableFiles() const
+{
+    return m_fileStatusWidget->getAllRevertableFiles();
+}
+
+QStringList HgTabWidget::getSelectedAddableFiles() const
+{
+    return m_fileStatusWidget->getSelectedAddableFiles();
+}
+
+QStringList HgTabWidget::getSelectedRemovableFiles() const
+{
+    return m_fileStatusWidget->getSelectedRemovableFiles();
+}
+
+QStringList HgTabWidget::getAllUnresolvedFiles() const
+{
+    return m_fileStatusWidget->getAllUnresolvedFiles();
+}
+
+void HgTabWidget::updateWorkFolderFileList(QString fileList)
+{
+    m_fileStates.parseStates(fileList);
+    m_fileStatusWidget->setFileStates(m_fileStates);
+}
+
+void HgTabWidget::setHaveMerge(bool haveMerge)
+{
+    if (m_haveMerge != haveMerge) {
+        m_haveMerge = haveMerge;
+        m_historyWidget->setShowUncommitted(haveChangesToCommit());
+        updateHistory();
+    }
+}
+
+void HgTabWidget::setNewLog(QString hgLogList)
+{
+    m_historyWidget->parseNewLog(hgLogList);
+    if (m_historyWidget->haveNewItems()) {
+        showHistoryTab();
+    }
+}
+
+void HgTabWidget::addIncrementalLog(QString hgLogList)
+{
+    m_historyWidget->parseIncrementalLog(hgLogList);
+    if (m_historyWidget->haveNewItems()) {
+        QSettings settings;
+        settings.beginGroup("Presentation");
+        if (settings.value("showHistoryAutomatically", true).toBool()) {
+            showHistoryTab();
+        }
+    }
+}
+
+void HgTabWidget::setLocalPath(QString workFolderPath)
+{
+    m_fileStatusWidget->setLocalPath(workFolderPath);
+}
+
+void HgTabWidget::showWorkTab()
+{
+    setCurrentWidget(m_fileStatusWidget);
+}
+
+void HgTabWidget::showHistoryTab()
+{
+    setCurrentWidget(m_historyWidget);
+}
+
+bool HgTabWidget::shouldShowAll() const
+{
+    return m_fileStatusWidget->shouldShowAll();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hgtabwidget.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,129 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 HGTABWIDGET_H
+#define HGTABWIDGET_H
+
+#include "changeset.h"
+#include "common.h"
+#include "filestates.h"
+
+#include <QMenu>
+#include <QListWidget>
+#include <QGroupBox>
+#include <QVBoxLayout>
+#include <QCheckBox>
+#include <QLabel>
+#include <QTabWidget>
+
+class FileStatusWidget;
+class HistoryWidget;
+
+class HgTabWidget: public QTabWidget
+{
+    Q_OBJECT
+
+public:
+    HgTabWidget(QWidget *parent, QString workFolderPath);
+
+    void updateWorkFolderFileList(QString fileList);
+
+    void setNewLog(QString hgLogList);
+    void addIncrementalLog(QString hgLogList);
+
+    void setLocalPath(QString workFolderPath);
+
+    void setCurrent(QStringList ids, QString branch);
+    void setClosedHeadIds(QSet<QString> ids);
+
+    void setHaveMerge(bool);
+
+    void clearAll();
+    void updateFileStates();
+    void updateHistory();
+
+    FileStates getFileStates() { return m_fileStates; }
+
+    bool canDiff() const;
+    bool canCommit() const;
+    bool canRevert() const;
+    bool canAdd() const;
+    bool canRemove() const;
+    bool canResolve() const;
+    bool canIgnore() const;
+    bool haveChangesToCommit() const;
+
+    QStringList getAllCommittableFiles() const;
+    QStringList getAllRevertableFiles() const;
+    QStringList getAllUnresolvedFiles() const;
+
+    QStringList getSelectedAddableFiles() const;
+    QStringList getSelectedRemovableFiles() const;
+
+    bool shouldShowAll() const;
+
+signals:
+    void selectionChanged();
+    void showAllChanged();
+
+    void commit();
+    void revert();
+    void diffWorkingFolder();
+    void showSummary();
+    void newBranch();
+    void noBranch();
+
+    void updateTo(QString id);
+    void diffToParent(QString id, QString parent);
+    void showSummary(Changeset *);
+    void diffToCurrent(QString id);
+    void mergeFrom(QString id);
+    void newBranch(QString id);
+    void closeBranch(QString id);
+    void tag(QString id);
+
+    void annotateFiles(QStringList);
+    void diffFiles(QStringList);
+    void commitFiles(QStringList);
+    void revertFiles(QStringList);
+    void renameFiles(QStringList);
+    void copyFiles(QStringList);
+    void addFiles(QStringList);
+    void removeFiles(QStringList);
+    void redoFileMerges(QStringList);
+    void markFilesResolved(QStringList);
+    void ignoreFiles(QStringList);
+    void unIgnoreFiles(QStringList);
+    void showIn(QStringList);
+
+public slots:
+    void clearSelections();
+    void showWorkTab();
+    void showHistoryTab();
+
+    void setSearchText(QString text);
+
+private:
+    FileStatusWidget *m_fileStatusWidget;
+    HistoryWidget *m_historyWidget;
+    FileStates m_fileStates;
+    bool m_haveMerge;
+
+    Changesets parseChangeSets(QString changeSetsStr);
+};
+
+#endif // HGTABWIDGET_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/historywidget.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,410 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "historywidget.h"
+
+#include "changesetscene.h"
+#include "changesetview.h"
+#include "panner.h"
+#include "grapher.h"
+#include "debug.h"
+#include "uncommitteditem.h"
+#include "findwidget.h"
+
+#include <iostream>
+
+#include <QGridLayout>
+#include <QSettings>
+
+HistoryWidget::HistoryWidget() :
+    m_showUncommitted(false),
+    m_refreshNeeded(false)
+{
+    m_panned = new ChangesetView;
+    m_panner = new Panner;
+
+    m_panned->setDragMode(QGraphicsView::ScrollHandDrag);
+    m_panned->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
+    m_panned->setCacheMode(QGraphicsView::CacheNone);
+
+    int row = 0;
+
+    QGridLayout *layout = new QGridLayout;
+    layout->setMargin(10);
+    layout->addWidget(m_panned, row, 0);
+    layout->addWidget(m_panner, row, 1);
+    m_panner->setMaximumWidth(80);
+    m_panner->connectToPanned(m_panned);
+
+    layout->setRowStretch(row, 20);
+
+    QSettings settings;
+    settings.beginGroup("Presentation");
+    bool showClosed = (settings.value("showclosedbranches", false).toBool());
+
+    QWidget *opts = new QWidget;
+    QGridLayout *optLayout = new QGridLayout(opts);
+    optLayout->setMargin(0);
+    layout->addWidget(opts, ++row, 0, 1, 2);
+
+    m_findWidget = new FindWidget(this);
+    optLayout->addWidget(m_findWidget, 0, 0, Qt::AlignLeft);
+    connect(m_findWidget, SIGNAL(findTextChanged(QString)),
+            this, SLOT(setSearchText(QString)));
+
+    m_showClosedBranches = new QCheckBox(tr("Show closed branches"), this);
+    m_showClosedBranches->setChecked(showClosed);
+    connect(m_showClosedBranches, SIGNAL(toggled(bool)), 
+            this, SLOT(showClosedChanged(bool)));
+    optLayout->addWidget(m_showClosedBranches, 0, 1, Qt::AlignRight);
+    m_showClosedBranches->hide();
+
+    setLayout(layout);
+}
+
+HistoryWidget::~HistoryWidget()
+{
+    clearChangesets();
+}
+
+QGraphicsScene *HistoryWidget::scene()
+{
+    return m_panned->scene();
+}
+
+void HistoryWidget::clearChangesets()
+{
+    foreach (Changeset *cs, m_changesets) delete cs;
+    m_changesets.clear();
+}
+
+void HistoryWidget::setCurrent(QStringList ids, QString branch,
+                               bool showUncommitted)
+{
+    if (m_currentIds == ids &&
+        m_currentBranch == branch &&
+        m_showUncommitted == showUncommitted) return;
+
+    DEBUG << "HistoryWidget::setCurrent: " << ids.size() << " ids, "
+          << "showUncommitted: " << showUncommitted << endl;
+
+    m_currentIds.clear();
+    m_currentBranch = branch;
+    m_showUncommitted = showUncommitted;
+
+    if (ids.empty()) return;
+
+    foreach (QString id, ids) {
+        m_currentIds.push_back(id);
+    }
+
+    m_refreshNeeded = true;
+}
+
+void HistoryWidget::setClosedHeadIds(QSet<QString> closed)
+{
+    if (closed == m_closedIds) return;
+    m_closedIds = closed;
+    m_showClosedBranches->setVisible(!closed.empty());
+    m_refreshNeeded = true;
+}
+
+void HistoryWidget::setShowUncommitted(bool showUncommitted)
+{
+    setCurrent(m_currentIds, m_currentBranch, showUncommitted);
+}
+
+void HistoryWidget::showClosedChanged(bool show)
+{
+    QSettings settings;
+    settings.beginGroup("Presentation");
+    settings.setValue("showclosedbranches", show);
+    layoutAll();
+}
+    
+void HistoryWidget::parseNewLog(QString log)
+{
+    DEBUG << "HistoryWidget::parseNewLog: log has " << log.length() << " chars" << endl;
+    Changesets csets = Changeset::parseChangesets(log);
+    DEBUG << "HistoryWidget::parseNewLog: log has " << csets.size() << " changesets" << endl;
+    replaceChangesets(csets);
+    m_refreshNeeded = true;
+}
+    
+void HistoryWidget::parseIncrementalLog(QString log)
+{
+    DEBUG << "HistoryWidget::parseIncrementalLog: log has " << log.length() << " chars" << endl;
+    Changesets csets = Changeset::parseChangesets(log);
+    DEBUG << "HistoryWidget::parseIncrementalLog: log has " << csets.size() << " changesets" << endl;
+    if (!csets.empty()) {
+        addChangesets(csets);
+    }
+    m_refreshNeeded = true;
+}
+
+void HistoryWidget::replaceChangesets(Changesets csets)
+{
+    QSet<QString> oldIds;
+    foreach (Changeset *cs, m_changesets) {
+        oldIds.insert(cs->id());
+    }
+
+    QSet<QString> newIds;
+    foreach (Changeset *cs, csets) {
+        if (!oldIds.contains(cs->id())) {
+            newIds.insert(cs->id());
+        }
+    }
+
+    if (newIds.size() == csets.size()) {
+        // completely new set, unrelated to the old: don't mark new
+        m_newIds.clear();
+    } else {
+        m_newIds = newIds;
+    }
+
+    clearChangesets();
+    m_changesets = csets;
+}
+
+void HistoryWidget::addChangesets(Changesets csets)
+{
+    m_newIds.clear();
+
+    if (csets.empty()) return;
+
+    foreach (Changeset *cs, csets) {
+        m_newIds.insert(cs->id());
+    }
+
+    DEBUG << "addChangesets: " << csets.size() << " new changesets have ("
+          << m_changesets.size() << " already)" << endl;
+
+    csets << m_changesets;
+    m_changesets = csets;
+}
+
+void HistoryWidget::update()
+{
+    if (m_refreshNeeded) {
+        layoutAll();
+    }
+}
+
+void HistoryWidget::clear()
+{
+    QGraphicsScene *oldScene = m_panned->scene();
+    m_panned->setScene(0);
+    delete oldScene;
+}
+
+void HistoryWidget::layoutAll()
+{
+    m_refreshNeeded = false;
+
+    setChangesetParents();
+
+    ChangesetScene *scene = new ChangesetScene();
+    QGraphicsScene *oldScene = m_panned->scene();
+
+    m_panned->setScene(0);
+    m_panner->setScene(0);
+
+    delete oldScene;
+
+    QGraphicsItem *toFocus = 0;
+
+    if (!m_changesets.empty()) {
+	Grapher g(scene);
+        g.setClosedHeadIds(m_closedIds);
+	try {
+	    g.layout(m_changesets,
+                     m_showUncommitted ? m_currentIds : QStringList(),
+                     m_currentBranch);
+	} catch (std::string s) {
+	    std::cerr << "Internal error: Layout failed: " << s << std::endl;
+	}
+        toFocus = g.getUncommittedItem();
+        if (!toFocus) {
+            if (!m_currentIds.empty()) {
+                for (int i = 0; i < m_currentIds.size(); ++i) {
+                    toFocus = g.getItemFor(m_currentIds[i]);
+                    if (toFocus != 0) {
+                        break;
+                    }
+                }
+            } else {
+                toFocus = g.getItemFor(m_changesets[0]);
+            }
+        }
+        if (!toFocus) {
+            for (int i = 0; i < m_changesets.size(); ++i) {
+                toFocus = g.getItemFor(m_changesets[i]);
+                if (toFocus != 0) {
+                    break;
+                }
+            }
+        }
+    }
+
+    m_panned->setScene(scene);
+    m_panner->setScene(scene);
+
+    updateNewAndCurrentItems();
+
+    if (toFocus) {
+        toFocus->ensureVisible();
+    }
+
+    if (m_searchText != "") {
+        updateSearchStatus();
+    }
+    connectSceneSignals();
+}
+
+void HistoryWidget::setChangesetParents()
+{
+    for (int i = 0; i < m_changesets.size(); ++i) {
+        Changeset *cs = m_changesets[i];
+        // Need to reset this, as Grapher::layout will recalculate it
+        // and we don't want to end up with twice the children for
+        // each parent...
+        cs->setChildren(QStringList());
+    }
+    for (int i = 0; i+1 < m_changesets.size(); ++i) {
+        Changeset *cs = m_changesets[i];
+        if (cs->parents().empty()) {
+            QStringList list;
+            list.push_back(m_changesets[i+1]->id());
+            cs->setParents(list);
+        }
+    }
+}
+
+void HistoryWidget::updateNewAndCurrentItems()
+{
+    QGraphicsScene *scene = m_panned->scene();
+    if (!scene) return;
+
+    QList<QGraphicsItem *> items = scene->items();
+    foreach (QGraphicsItem *it, items) {
+
+        ChangesetItem *csit = dynamic_cast<ChangesetItem *>(it);
+        if (!csit) continue;
+
+        QString id = csit->getChangeset()->id();
+
+        bool current = m_currentIds.contains(id);
+        if (current) {
+            DEBUG << "id " << id << " is current" << endl;
+        }
+        bool newid = m_newIds.contains(id);
+        if (newid) {
+            DEBUG << "id " << id << " is new" << endl;
+        }
+
+        if (csit->isCurrent() != current ||
+            csit->isNew() != newid) {
+            csit->setCurrent(current);
+            csit->setNew(newid);
+            csit->update();
+        }
+    }
+}
+
+void HistoryWidget::setSearchText(QString text)
+{
+    if (m_searchText == text) return;
+    m_searchText = text;
+    updateSearchStatus();
+}
+
+void HistoryWidget::updateSearchStatus()
+{
+    QGraphicsScene *scene = m_panned->scene();
+    if (!scene) return;
+
+    ChangesetItem *toFocus = 0;
+
+    QList<QGraphicsItem *> items = scene->items();
+    foreach (QGraphicsItem *it, items) {
+
+        ChangesetItem *csit = dynamic_cast<ChangesetItem *>(it);
+        if (!csit) continue;
+        
+        bool matched = csit->matchSearchText(m_searchText);
+        if (matched && (!toFocus || csit->row() < toFocus->row())) {
+            toFocus = csit;
+        }
+        csit->update();
+    }
+
+    if (toFocus) {
+        toFocus->ensureVisible();
+    }
+}
+
+void HistoryWidget::connectSceneSignals()
+{
+    ChangesetScene *scene = qobject_cast<ChangesetScene *>(m_panned->scene());
+    if (!scene) return;
+    
+    connect(scene, SIGNAL(commit()),
+            this, SIGNAL(commit()));
+    
+    connect(scene, SIGNAL(revert()),
+            this, SIGNAL(revert()));
+    
+    connect(scene, SIGNAL(diffWorkingFolder()),
+            this, SIGNAL(diffWorkingFolder()));
+
+    connect(scene, SIGNAL(showSummary()),
+            this, SIGNAL(showSummary()));
+
+    connect(scene, SIGNAL(showWork()),
+            this, SIGNAL(showWork()));
+
+    connect(scene, SIGNAL(newBranch()),
+            this, SIGNAL(newBranch()));
+
+    connect(scene, SIGNAL(noBranch()),
+            this, SIGNAL(noBranch()));
+    
+    connect(scene, SIGNAL(updateTo(QString)),
+            this, SIGNAL(updateTo(QString)));
+
+    connect(scene, SIGNAL(diffToCurrent(QString)),
+            this, SIGNAL(diffToCurrent(QString)));
+
+    connect(scene, SIGNAL(diffToParent(QString, QString)),
+            this, SIGNAL(diffToParent(QString, QString)));
+
+    connect(scene, SIGNAL(showSummary(Changeset *)),
+            this, SIGNAL(showSummary(Changeset *)));
+
+    connect(scene, SIGNAL(mergeFrom(QString)),
+            this, SIGNAL(mergeFrom(QString)));
+
+    connect(scene, SIGNAL(newBranch(QString)),
+            this, SIGNAL(newBranch(QString)));
+
+    connect(scene, SIGNAL(closeBranch(QString)),
+            this, SIGNAL(closeBranch(QString)));
+
+    connect(scene, SIGNAL(tag(QString)),
+            this, SIGNAL(tag(QString)));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/historywidget.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,103 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 HISTORYWIDGET_H
+#define HISTORYWIDGET_H
+
+#include "changeset.h"
+
+#include <QWidget>
+#include <QSet>
+#include <QCheckBox>
+
+class Panned;
+class Panner;
+class UncommittedItem;
+class QGraphicsScene;
+class FindWidget;
+
+class HistoryWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    HistoryWidget();
+    virtual ~HistoryWidget();
+
+    void setCurrent(QStringList ids, QString branch, bool showUncommitted);
+    void setShowUncommitted(bool showUncommitted);
+    void setClosedHeadIds(QSet<QString> closed);
+
+    void parseNewLog(QString log);
+    void parseIncrementalLog(QString log);
+
+    bool haveNewItems() const { return !m_newIds.empty(); }
+
+signals:
+    void commit();
+    void revert();
+    void diffWorkingFolder();
+    void showSummary();
+    void showWork();
+    void newBranch();
+    void noBranch();
+
+    void updateTo(QString id);
+    void diffToParent(QString id, QString parent);
+    void showSummary(Changeset *);
+    void diffToCurrent(QString id);
+    void mergeFrom(QString id);
+    void newBranch(QString id);
+    void closeBranch(QString id);
+    void tag(QString id);
+
+private slots:
+    void showClosedChanged(bool);
+
+public slots:
+    void setSearchText(QString);
+    void update();
+    void clear();
+
+private:
+    Changesets m_changesets;
+    QStringList m_currentIds;
+    QString m_currentBranch;
+    QSet<QString> m_newIds;
+    QSet<QString> m_closedIds;
+    bool m_showUncommitted;
+    bool m_refreshNeeded;
+
+    FindWidget *m_findWidget;
+    Panned *m_panned;
+    Panner *m_panner;
+    QCheckBox *m_showClosedBranches;
+
+    QString m_searchText;
+
+    QGraphicsScene *scene();
+    void clearChangesets();
+    void replaceChangesets(Changesets);
+    void addChangesets(Changesets);
+    void layoutAll();
+    void setChangesetParents();
+    void updateNewAndCurrentItems();
+    void connectSceneSignals();
+    void updateSearchStatus();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/incomingdialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,89 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 "incomingdialog.h"
+#include "changeset.h"
+#include "common.h"
+
+#include <QScrollArea>
+#include <QApplication>
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QGridLayout>
+#include <QStyle>
+
+IncomingDialog::IncomingDialog(QWidget *w, QString text) :
+    QDialog(w)
+{
+    QString head;
+    QString body;
+    bool scroll;
+
+    Changesets csets = Changeset::parseChangesets(text);
+    if (csets.empty()) {
+	head = tr("No changes waiting to pull");
+	if (text.trimmed() != "") {
+	    body = QString("<p>%1</p><code>%2</code>")
+		.arg(tr("The command output was:"))
+		.arg(xmlEncode(text).replace("\n", "<br>"));
+	} else {
+            body = tr("<qt>Your local repository already contains all changes found in the remote repository.</qt>");
+        }
+	scroll = false;
+    } else {
+        head = tr("There are %n change(s) ready to pull", "", csets.size());
+	foreach (Changeset *cs, csets) {
+	    body += cs->formatHtml() + "<p>";
+	    delete cs;
+	}
+	scroll = true;
+    }
+
+    QGridLayout *layout = new QGridLayout;
+    setLayout(layout);
+
+    QLabel *info = new QLabel;
+    QStyle *style = qApp->style();
+    int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this);
+    info->setPixmap(style->standardIcon(QStyle::SP_MessageBoxInformation, 0, this)
+		    .pixmap(iconSize, iconSize));
+    layout->addWidget(info, 0, 0, 2, 1);
+
+    QLabel *headLabel = new QLabel(QString("<qt><h3>%1</h3></qt>").arg(head));
+    layout->addWidget(headLabel, 0, 1);
+
+    QLabel *textLabel = new QLabel(body);
+    if (csets.empty()) textLabel->setWordWrap(true);
+
+    if (scroll) {
+	QScrollArea *sa = new QScrollArea;
+	layout->addWidget(sa, 1, 1);
+	layout->setRowStretch(1, 20);
+	sa->setWidget(textLabel);
+    } else {
+	layout->addWidget(textLabel, 1, 1);
+    }
+
+    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
+    connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
+    layout->addWidget(bb, 2, 0, 1, 2);
+
+    layout->setColumnStretch(1, 20);
+    setMinimumWidth(400);
+}
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/incomingdialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,31 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 INCOMING_DIALOG_H
+#define INCOMING_DIALOG_H
+
+#include <QDialog>
+
+class IncomingDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    IncomingDialog(QWidget *parent, QString incoming);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/logparser.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,55 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "logparser.h"
+
+#include "common.h"
+#include "debug.h"
+
+#include <QStringList>
+#include <QRegExp>
+
+LogParser::LogParser(QString text, QString separator) :
+    m_text(text), m_sep(separator)
+{
+    m_text.replace("\r\n", "\n");
+}
+
+QStringList LogParser::split()
+{
+    return m_text.split("\n\n", QString::SkipEmptyParts);
+}
+
+LogList LogParser::parse()
+{
+    LogList results;
+    QRegExp re(QString("^(\\w+)\\s*%1\\s+([^\\s].*)$").arg(m_sep));
+    QStringList entries = split();
+    foreach (QString entry, entries) {
+        LogEntry dictionary;
+        QStringList lines = entry.split('\n');
+        foreach (QString line, lines) {
+            if (re.indexIn(line) == 0) {
+                QString key = re.cap(1);
+                QString value = re.cap(2);
+                dictionary[key.trimmed()] = uniDecode(value);
+            }
+        }
+        results.push_back(dictionary);
+    }
+    return results;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/logparser.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,42 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 LOGPARSER_H
+#define LOGPARSER_H
+
+#include <QObject>
+#include <QString>
+#include <QList>
+#include <QMap>
+
+typedef QMap<QString, QString> LogEntry;
+typedef QList<LogEntry> LogList;
+
+class LogParser : public QObject
+{
+public:
+    LogParser(QString text, QString separator = ":");
+
+    QStringList split();
+    LogList parse();
+
+private:
+    QString m_text;
+    QString m_sep;
+};
+
+#endif // LOGPARSER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,90 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "mainwindow.h"
+#include "common.h"
+#include "debug.h"
+#include "version.h"
+
+#include <QApplication>
+#include <QTranslator>
+#include <QDir>
+
+using std::cout;
+using std::endl;
+
+int main(int argc, char *argv[])
+{
+    if (argc == 2 &&
+        (!strcmp(argv[1], "-v") ||
+         !strcmp(argv[1], "--version"))) {
+        cout << "EasyMercurial v" << EASYHG_VERSION << "\n"
+             << "Copyright (c) 2010 Jari Korhonen\n"
+             << "Copyright (c) 2013 Chris Cannam\n"
+             << "Copyright (c) 2013 Queen Mary, University of London\n"
+             << "This program is free software; you can redistribute it and/or\n"
+             << "modify it under the terms of the GNU General Public License as\n"
+             << "published by the Free Software Foundation; either version 2 of the\n"
+             << "License, or (at your option) any later version.  See the file\n"
+             << "COPYING included with this distribution for more information."
+             << endl;
+        return 0;
+    }
+
+    QApplication app(argc, argv);
+
+    QApplication::setOrganizationName("easymercurial");
+    QApplication::setOrganizationDomain("easymercurial.org");
+    QApplication::setApplicationName(QApplication::tr("EasyMercurial"));
+
+#ifdef Q_OS_MAC
+    // Mac doesn't align menu labels when icons are shown: result is messy
+    app.setAttribute(Qt::AA_DontShowIconsInMenus);
+#endif
+
+    // Lose our controlling terminal (so we can provide a new pty to
+    // capture password requests)
+    loseControllingTerminal();
+
+    installSignalHandlers();
+
+    QTranslator translator;
+    QString language = QLocale::system().name();
+    if (language == "C") language = "en";
+    QString trname = QString("easyhg_%1").arg(language);
+    translator.load(trname, ":");
+    app.installTranslator(&translator);
+
+    QStringList args = app.arguments();
+
+    QString myDirPath = QFileInfo(QDir::current().absoluteFilePath(args[0]))
+        .canonicalPath();
+
+    MainWindow mainWin(myDirPath);
+    mainWin.show();
+
+    if (args.size() == 2) {
+        QString path = args[1];
+        DEBUG << "Opening " << args[1] << endl;
+        if (QDir(path).exists()) {
+            path = QDir(path).canonicalPath();
+            mainWin.open(path);
+        }
+    }
+
+    return app.exec();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mainwindow.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,3266 @@
+ /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 <QStringList>
+#include <QDir>
+#include <QNetworkInterface>
+#include <QHostAddress>
+#include <QHostInfo>
+#include <QDesktopServices>
+#include <QStatusBar>
+#include <QMessageBox>
+#include <QMenuBar>
+#include <QApplication>
+#include <QToolBar>
+#include <QToolButton>
+#include <QSettings>
+#include <QInputDialog>
+#include <QWidgetAction>
+#include <QRegExp>
+#include <QShortcut>
+#include <QUrl>
+#include <QDialogButtonBox>
+#include <QTimer>
+#include <QTextBrowser>
+
+#include "mainwindow.h"
+#include "multichoicedialog.h"
+#include "startupdialog.h"
+#include "colourset.h"
+#include "debug.h"
+#include "logparser.h"
+#include "confirmcommentdialog.h"
+#include "incomingdialog.h"
+#include "settingsdialog.h"
+#include "moreinformationdialog.h"
+#include "annotatedialog.h"
+#include "version.h"
+#include "workstatuswidget.h"
+#include "hgignoredialog.h"
+#include "versiontester.h"
+#include "fswatcher.h"
+
+
+MainWindow::MainWindow(QString myDirPath) :
+    m_myDirPath(myDirPath),
+    m_helpDialog(0)
+{
+    setWindowIcon(QIcon(":images/easyhg-icon.png"));
+
+    QString wndTitle;
+
+    m_showAllFiles = false;
+
+    m_fsWatcher = new FsWatcher();
+    m_fsWatcherToken = m_fsWatcher->getNewToken();
+    m_commandSequenceInProgress = false;
+    connect(m_fsWatcher, SIGNAL(changed()), this, SLOT(checkFilesystem()));
+
+    m_commitsSincePush = 0;
+    m_shouldHgStat = true;
+
+    createActions();
+    createMenus();
+    createToolBars();
+    createStatusBar();
+
+    m_runner = new HgRunner(m_myDirPath, this);
+    connect(m_runner, SIGNAL(commandStarting(HgAction)),
+            this, SLOT(commandStarting(HgAction)));
+    connect(m_runner, SIGNAL(commandCompleted(HgAction, QString)),
+            this, SLOT(commandCompleted(HgAction, QString)));
+    connect(m_runner, SIGNAL(commandFailed(HgAction, QString, QString)),
+            this, SLOT(commandFailed(HgAction, QString, QString)));
+    connect(m_runner, SIGNAL(commandCancelled(HgAction)),
+            this, SLOT(commandCancelled(HgAction)));
+    statusBar()->addPermanentWidget(m_runner);
+
+    setWindowTitle(tr("EasyMercurial"));
+
+    m_remoteRepoPath = "";
+    m_workFolderPath = "";
+
+    readSettings();
+
+    m_justMerged = false;
+
+    QWidget *central = new QWidget(this);
+    setCentralWidget(central);
+
+    QGridLayout *cl = new QGridLayout(central);
+    int row = 0;
+
+#ifndef Q_OS_MAC
+    cl->setMargin(0);
+#endif
+
+    m_workStatus = new WorkStatusWidget(this);
+    cl->addWidget(m_workStatus, row++, 0);
+
+    m_hgTabs = new HgTabWidget(central, m_workFolderPath);
+    connectTabsSignals();
+
+    cl->addWidget(m_hgTabs, row++, 0, 1, 2);
+
+    connect(m_hgTabs, SIGNAL(selectionChanged()),
+            this, SLOT(enableDisableActions()));
+    connect(m_hgTabs, SIGNAL(showAllChanged()),
+            this, SLOT(showAllChanged()));
+
+    connectActions();
+    clearState();
+    enableDisableActions();
+
+    if (m_firstStart) {
+        startupDialog();
+    }
+
+    SettingsDialog::findDefaultLocations(m_myDirPath);
+
+    ColourSet *cs = ColourSet::instance();
+    cs->clearDefaultNames();
+    cs->addDefaultName("");
+    cs->addDefaultName("default");
+    cs->addDefaultName(getUserInfo());
+
+    VersionTester *vt = new VersionTester
+        ("easyhg.org", "/latest-version.txt", EASYHG_VERSION);
+    connect(vt, SIGNAL(newerVersionAvailable(QString)),
+            this, SLOT(newerVersionAvailable(QString)));
+
+    hgTest();
+    updateRecentMenu();
+}
+
+
+void MainWindow::closeEvent(QCloseEvent *)
+{
+    writeSettings();
+    delete m_fsWatcher;
+}
+
+
+void MainWindow::resizeEvent(QResizeEvent *)
+{
+}
+
+
+QString MainWindow::getUserInfo() const
+{
+    QSettings settings;
+    settings.beginGroup("User Information");
+    QString name = settings.value("name", getUserRealName()).toString();
+    QString email = settings.value("email", "").toString();
+
+    QString identifier;
+
+    if (email != "") {
+	identifier = QString("%1 <%2>").arg(name).arg(email);
+    } else {
+	identifier = name;
+    }
+
+    return identifier;
+}
+
+void MainWindow::about()
+{
+   QMessageBox::about(this, tr("About EasyMercurial"),
+                      tr("<qt><h2>EasyMercurial v%1</h2>"
+#ifdef Q_OS_MAC
+                         "<font size=-1>"
+#endif
+                         "<p>EasyMercurial is a simple user interface for the "
+                         "Mercurial</a> version control system.</p>"
+                         "<h4>Credits and Copyright</h4>"
+                         "<p>Development carried out by Chris Cannam for "
+                         "SoundSoftware.ac.uk at the Centre for Digital Music, "
+                         "Queen Mary, University of London.</p>"
+                         "<p>EasyMercurial is based on HgExplorer by "
+                         "Jari Korhonen, with thanks.</p>"
+                         "<p style=\"margin-left: 2em;\">"
+                         "Copyright &copy; 2013 Queen Mary, University of London.<br>"
+                         "Copyright &copy; 2010 Jari Korhonen.<br>"
+                         "Copyright &copy; 2013 Chris Cannam."
+                         "</p>"
+                         "<p style=\"margin-left: 2em;\">"
+                         "This program requires Mercurial, by Matt Mackall and others.<br>"
+                         "This program uses Qt by Nokia.<br>"
+                         "This program uses Nuvola icons by David Vignoni.<br>"
+                         "This program may use KDiff3 by Joachim Eibl.<br>"
+                         "This program may use PyQt by River Bank Computing.<br>"
+                         "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others."
+                         "</p>"
+                         "<h4>License</h4>"
+                         "<p>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.</p>"
+#ifdef Q_OS_MAC
+                         "</font>"
+#endif
+                          ).arg(EASYHG_VERSION));
+}
+
+void MainWindow::clearSelections()
+{
+    m_hgTabs->clearSelections();
+}
+
+void MainWindow::showAllChanged()
+{
+    hgQueryPaths();
+}
+
+void MainWindow::hgRefresh()
+{
+    clearState();
+    hgQueryPaths();
+}
+
+void MainWindow::hgTest()
+{
+    QStringList params;
+    params << "--version";
+    // The path is not necessarily set here, but we need something for
+    // this test or else HgRunner will (cautiously) refuse to run
+    QString path = m_myDirPath;
+    if (path == "") path = QDir::homePath();
+    m_runner->requestAction(HgAction(ACT_TEST_HG, path, params));
+}
+
+void MainWindow::hgTestExtension()
+{
+    QStringList params;
+    params << "--version";
+    // The path is not necessarily set here, but we need something for
+    // this test or else HgRunner will (cautiously) refuse to run
+    QString path = m_myDirPath;
+    if (path == "") path = QDir::homePath();
+    m_runner->requestAction(HgAction(ACT_TEST_HG_EXT, path, params));
+}
+
+void MainWindow::hgStat()
+{
+    QStringList params;
+
+    // We always stat all files, regardless of whether we're showing
+    // them all, because we need them for the filesystem monitor
+    params << "stat" << "-A";
+
+    m_lastStatOutput = "";
+
+    // We're about to do a stat, so we can silently bring ourselves
+    // up-to-date on any file changes to this point
+    (void)m_fsWatcher->getChangedPaths(m_fsWatcherToken);
+
+    m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params));
+}
+
+void MainWindow::hgQueryPaths()
+{
+    m_showAllFiles = m_hgTabs->shouldShowAll();
+
+    // Quickest is to just read the file
+
+    QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc");
+
+    QString path;
+
+    if (hgrc.exists()) {
+        QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat);
+        s.beginGroup("paths");
+        path = s.value("default").toString();
+    }
+
+//    std::cerr << "hgQueryPaths: setting m_remoteRepoPath to " << m_remoteRepoPath << " from file " << hgrc.absoluteFilePath() << std::endl;
+    
+    m_remoteRepoPath = path;
+
+    // We have to do this here, because commandCompleted won't be called
+    MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
+    MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
+    updateWorkFolderAndRepoNames();
+    
+    hgQueryBranch();
+    return;
+
+/* The classic method!
+
+    QStringList params;
+    params << "paths";
+    m_runner->requestAction(HgAction(ACT_QUERY_PATHS, m_workFolderPath, params));
+*/
+}
+
+void MainWindow::hgQueryBranch()
+{
+    // Quickest is to just read the file
+
+    QFile hgbr(m_workFolderPath + "/.hg/branch");
+
+    QString br = "default";
+
+    if (hgbr.exists() && hgbr.open(QFile::ReadOnly)) {
+        QByteArray ba = hgbr.readLine();
+        br = QString::fromUtf8(ba).trimmed();
+    }
+    
+    m_currentBranch = br;
+    
+    // We have to do this here, because commandCompleted won't be called
+    hgStat();
+    return;
+
+/* The classic method!
+
+    QStringList params;
+    params << "branch";
+    m_runner->requestAction(HgAction(ACT_QUERY_BRANCH, m_workFolderPath, params));
+*/
+}
+
+void MainWindow::hgQueryHeadsActive()
+{
+    QStringList params;
+    params << "heads";
+    m_runner->requestAction(HgAction(ACT_QUERY_HEADS_ACTIVE, m_workFolderPath, params));
+}
+
+void MainWindow::hgQueryHeads()
+{
+    QStringList params;
+    // On empty repos, "hg heads" will fail -- we don't care about
+    // that.  Use --closed option so as to include closed branches;
+    // otherwise we'll be stuck if the user updates into one, and our
+    // incremental log will end up with spurious stuff in it because
+    // we won't be pruning at the ends of closed branches
+    params << "heads" << "--closed";
+    m_runner->requestAction(HgAction(ACT_QUERY_HEADS, m_workFolderPath, params));
+}
+
+void MainWindow::hgLog()
+{
+    QSettings settings;
+    settings.beginGroup("Presentation");
+
+    QStringList params;
+    params << "log";
+    params << "--date";
+    params << settings.value("datefrom", QDate(2000, 1, 1)).toDate().toString("yyyy-MM-dd") + " to " + QDate::currentDate().toString("yyyy-MM-dd");
+    params << "--template";
+    params << Changeset::getLogTemplate();
+    
+    m_runner->requestAction(HgAction(ACT_LOG, m_workFolderPath, params));
+}
+
+void MainWindow::hgLogIncremental(QStringList prune)
+{
+    // Sometimes we can be called with prune empty -- it represents
+    // the current heads, but if we have none already and for some
+    // reason are being prompted for an incremental update, we may run
+    // into trouble.  In that case, make this a full log instead
+
+    if (prune.empty()) {
+        hgLog();
+        return;
+    }
+
+    QStringList params;
+    params << "log";
+
+    foreach (QString p, prune) {
+        params << "--prune" << Changeset::hashOf(p);
+    }
+        
+    params << "--template";
+    params << Changeset::getLogTemplate();
+    
+    m_runner->requestAction(HgAction(ACT_LOG_INCREMENTAL, m_workFolderPath, params));
+}
+
+void MainWindow::hgQueryParents()
+{
+    QStringList params;
+    params << "parents";
+    m_runner->requestAction(HgAction(ACT_QUERY_PARENTS, m_workFolderPath, params));
+}
+
+void MainWindow::hgAnnotateFiles(QStringList files)
+{
+    QStringList params;
+    
+    if (!files.isEmpty()) {
+        params << "annotate" << "-udqc" << "--" << files;
+        m_runner->requestAction(HgAction(ACT_ANNOTATE, m_workFolderPath, params));
+    }
+}
+
+void MainWindow::hgResolveList()
+{
+    QStringList params;
+
+    params << "resolve" << "--list";
+    m_runner->requestAction(HgAction(ACT_RESOLVE_LIST, m_workFolderPath, params));
+}
+
+void MainWindow::hgAdd()
+{
+    // hgExplorer permitted adding "all" files -- I'm not sure
+    // that one is a good idea, let's require the user to select
+
+    hgAddFiles(m_hgTabs->getSelectedAddableFiles());
+}
+
+void MainWindow::hgAddFiles(QStringList files)
+{
+    QStringList params;
+
+    if (!files.empty()) {
+        params << "add" << "--" << files;
+        m_runner->requestAction(HgAction(ACT_ADD, m_workFolderPath, params));
+    }
+}
+
+void MainWindow::hgRemove()
+{
+    hgRemoveFiles(m_hgTabs->getSelectedRemovableFiles());
+}
+
+void MainWindow::hgRemoveFiles(QStringList files)
+{
+    QStringList params;
+
+    if (!files.empty()) {
+        params << "remove" << "--after" << "--force" << "--" << files;
+        m_runner->requestAction(HgAction(ACT_REMOVE, m_workFolderPath, params));
+    }
+}
+
+void MainWindow::hgCommit()
+{
+    hgCommitFiles(QStringList());
+}
+
+void MainWindow::hgCommitFiles(QStringList files)
+{
+    QStringList params;
+    QString comment;
+
+    if (m_justMerged) {
+        comment = m_mergeCommitComment;
+    }
+
+    QStringList allFiles = m_hgTabs->getAllCommittableFiles();
+    QStringList reportFiles = files;
+    if (reportFiles.empty()) {
+        reportFiles = allFiles;
+    }
+
+    QString subsetNote;
+    if (reportFiles != allFiles) {
+        subsetNote = tr("<p><b>Note:</b> you are committing only the files you have selected, not all of the files that have been changed!");
+    }
+    
+    QString cf(tr("Commit files"));
+
+    QString branchText;
+    if (m_currentBranch == "" || m_currentBranch == "default") {
+        branchText = tr("the default branch");
+    } else {
+        branchText = tr("branch \"%1\"").arg(m_currentBranch);
+    }
+
+    if (ConfirmCommentDialog::confirmAndGetLongComment
+        (this,
+         cf,
+         tr("<h3>%1</h3><p>%2%3").arg(cf)
+         .arg(tr("You are about to commit changes to the following files in %1:").arg(branchText))
+         .arg(subsetNote),
+         tr("<h3>%1</h3><p>%2%3").arg(cf)
+         .arg(tr("You are about to commit changes to %n file(s) in %1.", "", reportFiles.size()).arg(branchText))
+         .arg(subsetNote),
+         reportFiles,
+         comment,
+         tr("Co&mmit"))) {
+
+        if (!m_justMerged && !files.empty()) {
+            // User wants to commit selected file(s) (and this is not
+            // merge commit, which would fail if we selected files)
+            params << "commit" << "--message" << comment
+                   << "--user" << getUserInfo() << "--" << files;
+        } else {
+            // Commit all changes
+            params << "commit" << "--message" << comment
+                   << "--user" << getUserInfo();
+        }
+        
+        m_runner->requestAction(HgAction(ACT_COMMIT, m_workFolderPath, params));
+        m_mergeCommitComment = "";
+    }
+}
+
+QString MainWindow::filterTag(QString tag)
+{
+    for(int i = 0; i < tag.size(); i++) {
+        if (tag[i].isLower() || tag[i].isUpper() ||
+            tag[i].isDigit() || (tag[i] == QChar('.'))) {
+            //ok
+        } else {
+            tag[i] = QChar('_');
+        }
+    }
+    return tag;
+}
+
+
+void MainWindow::hgNewBranch()
+{
+    QStringList params;
+    QString branch;
+
+    if (ConfirmCommentDialog::confirmAndGetShortComment
+        (this,
+         tr("New Branch"),
+         tr("Enter new branch name:"),
+         branch,
+         tr("Start &Branch"))) {
+        if (!branch.isEmpty()) {//!!! do something better if it is empty
+
+            params << "branch" << filterTag(branch);
+            m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
+        }
+    }
+}
+
+void MainWindow::hgNoBranch()
+{
+    if (m_currentParents.empty()) return;
+
+    QString parentBranch = m_currentParents[0]->branch();
+    if (parentBranch == "") parentBranch = "default";
+
+    QStringList params;
+    params << "branch" << parentBranch;
+    m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
+}
+
+void MainWindow::hgCloseBranch()
+{
+    QStringList params;
+
+    //!!! how to ensure this doesn't happen when uncommitted changes present?
+
+    QString cf(tr("Close branch"));
+    QString comment;
+
+    QString defaultWarning;
+
+    bool haveSprouts = (m_hgMergeAct->isEnabled());
+
+    QString branchText;
+    if (m_currentBranch == "" || m_currentBranch == "default") {
+        branchText = tr("the default branch");
+        if (!haveSprouts) {
+            defaultWarning = tr("<p><b>Warning:</b> you are asking to close the default branch. This is not usually a good idea!</p>");
+        }
+    } else {
+        branchText = tr("branch \"%1\"").arg(m_currentBranch);
+    }
+
+    if (haveSprouts) {
+        branchText = tr("a sub-branch of %1").arg(branchText);
+    }
+
+    if (ConfirmCommentDialog::confirmAndGetLongComment
+        (this,
+         cf,
+         tr("<h3>%1</h3><p>%2%3").arg(cf)
+         .arg(tr("You are about to close %1.<p>This branch will be marked as closed and hidden from the history view.<p>You will still be able to see if it you select \"Show closed branches\" in the history view, and it will be reopened if you commit to it.<p>Please enter your comment for the commit log:").arg(branchText))
+         .arg(defaultWarning),
+         comment,
+         tr("C&lose branch"))) {
+
+        params << "commit" << "--message" << comment
+               << "--user" << getUserInfo() << "--close-branch";
+        
+        m_runner->requestAction(HgAction(ACT_CLOSE_BRANCH, m_workFolderPath, params));
+    }
+}
+
+void MainWindow::hgTag(QString id)
+{
+    QStringList params;
+    QString tag;
+
+    if (ConfirmCommentDialog::confirmAndGetShortComment
+        (this,
+         tr("Tag"),
+         tr("Enter tag:"),
+         tag,
+         tr("Add &Tag"))) {
+        if (!tag.isEmpty()) {//!!! do something better if it is empty
+
+            params << "tag" << "--user" << getUserInfo();
+            params << "--rev" << Changeset::hashOf(id) << filterTag(tag);
+            
+            m_runner->requestAction(HgAction(ACT_TAG, m_workFolderPath, params));
+        }
+    }
+}
+
+void MainWindow::initHgIgnore()
+{
+    if (!QDir(m_workFolderPath).exists()) return;
+    QString hgIgnorePath = m_workFolderPath + "/.hgignore";
+
+    QFile f(hgIgnorePath);
+    if (!f.exists()) {
+        f.open(QFile::WriteOnly);
+        QTextStream *ts = new QTextStream(&f);
+        *ts << "syntax: glob\n";
+        delete ts;
+        f.close();
+    }
+}    
+
+void MainWindow::hgIgnore()
+{
+    // hgExplorer permitted adding "all" files -- I'm not sure
+    // that one is a good idea, let's require the user to select
+
+    hgIgnoreFiles(m_hgTabs->getSelectedAddableFiles());
+}
+
+void MainWindow::hgEditIgnore()
+{
+    if (!QDir(m_workFolderPath).exists()) return;
+
+    initHgIgnore();
+
+    QString hgIgnorePath = m_workFolderPath + "/.hgignore";
+
+    QFile f(hgIgnorePath);
+    if (!f.exists()) return; // shouldn't happen (after initHgIgnore called)
+
+    if (!f.open(QFile::ReadOnly)) return;
+    QTextStream sin(&f);
+    QString all = sin.readAll();
+    f.close();
+
+    QDialog d;
+    QGridLayout layout;
+    d.setLayout(&layout);
+
+    int row = 0;
+    layout.addWidget(new QLabel(tr("<qt><h3>Ignored File Patterns</h3></qt>")), row++, 0);//!!! todo: link to Hg docs?
+
+    QTextEdit ed;
+    ed.setAcceptRichText(false);
+    ed.setLineWrapMode(QTextEdit::NoWrap);
+    layout.setRowStretch(row, 10);
+    layout.addWidget(&ed, row++, 0);
+    
+    QDialogButtonBox bb(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
+    connect(bb.button(QDialogButtonBox::Save), SIGNAL(clicked()),
+            &d, SLOT(accept()));
+    connect(bb.button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
+            &d, SLOT(reject()));
+    layout.addWidget(&bb, row++, 0);
+
+    ed.document()->setPlainText(all);
+
+    d.resize(QSize(300, 400));
+
+    if (d.exec() == QDialog::Accepted) {
+        if (!f.open(QFile::WriteOnly | QFile::Truncate)) {
+            QMessageBox::critical(this, tr("Write failed"),
+                                  tr("Failed to open file %1 for writing")
+                                  .arg(f.fileName()));
+            return;
+        }
+        QTextStream sout(&f);
+        sout << ed.document()->toPlainText();
+        f.close();
+    }
+}
+
+static QString regexEscape(QString filename)
+{
+    return filename
+        .replace(".", "\\.")
+        .replace("[", "\\[")
+        .replace("]", "\\]")
+        .replace("(", "\\(")
+        .replace(")", "\\)")
+        .replace("?", "\\?");
+}
+
+void MainWindow::hgIgnoreFiles(QStringList files)
+{
+    if (!QDir(m_workFolderPath).exists() || files.empty()) return;
+
+    // we should:
+    //
+    // * show the user the list of file names selected
+    // 
+    // * offer a choice (depending on the files selected?)
+    // 
+    //   - ignore only these files
+    //
+    //   - ignore files with these names, in any subdirectories?
+    //
+    //   - ignore all files with this extension (if they have a common
+    //     extension)?
+    //   
+    //   - ignore all files with these extensions (if they have any
+    //     extensions?)
+
+    DEBUG << "MainWindow::hgIgnoreFiles: File names are:" << endl;
+    foreach (QString file, files) DEBUG << file << endl;
+
+    QSet<QString> suffixes;
+    foreach (QString file, files) {
+        QString s = QFileInfo(file).suffix();
+        if (s != "") suffixes.insert(s);
+    }
+
+    QString directory;
+    int dirCount = 0;
+    foreach (QString file, files) {
+        QString d = QFileInfo(file).path();
+        if (d != directory) {
+            ++dirCount;
+            directory = d;
+        }
+    }
+    if (dirCount != 1 || directory == ".") directory = "";
+
+    HgIgnoreDialog::IgnoreType itype =
+        HgIgnoreDialog::confirmIgnore
+        (this, files, QStringList::fromSet(suffixes), directory);
+
+    DEBUG << "hgIgnoreFiles: Ignore type is " << itype << endl;
+
+    if (itype == HgIgnoreDialog::IgnoreNothing) return;
+
+    // Now, .hgignore can be switched from regex to glob syntax
+    // part-way through -- and glob is much simpler for us, so we
+    // should do that if it's in regex mode at the end of the file.
+
+    initHgIgnore();
+
+    QString hgIgnorePath = m_workFolderPath + "/.hgignore";
+
+    // hgignore file should now exist (initHgIgnore should have
+    // created it if it didn't).  Check for glob status first
+
+    QFile f(hgIgnorePath);
+    if (!f.exists()) {
+        std::cerr << "MainWindow::ignoreFiles: Internal error: .hgignore file not found (even though we were supposed to have created it)" << std::endl;
+        return;
+    }
+
+    f.open(QFile::ReadOnly);
+    bool glob = false;
+    bool cr = false; // whether the last line examined ended with a CR
+    while (!f.atEnd()) {
+        QByteArray ba = f.readLine();
+        QString s = QString::fromLocal8Bit(ba);
+        cr = (s.endsWith('\n') || s.endsWith('\r'));
+        s = s.trimmed();
+        if (s.startsWith("syntax:")) {
+            if (s.endsWith("glob")) {
+                glob = true;
+            } else {
+                glob = false;
+            }
+        }
+    }
+    f.close();
+
+    f.open(QFile::Append);
+    QTextStream out(&f);
+
+    if (!cr) {
+        out << endl;
+    }
+
+    if (!glob) {
+        out << "syntax: glob" << endl;
+    }
+
+    QString info = "<qt><h3>" + tr("Ignored files") + "</h3><p>";
+    info += tr("The following lines have been added to the .hgignore file for this working copy:");
+    info += "</p><code>";
+
+    QStringList args;
+    if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) {
+        args = QStringList::fromSet(suffixes);
+    } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) {
+        args = files;
+    } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) {
+        QSet<QString> names;
+        foreach (QString f, files) {
+            names << QFileInfo(f).fileName();
+        }
+        args = QStringList::fromSet(names);
+    } else if (itype == HgIgnoreDialog::IgnoreWholeDirectory) {
+        args << directory;
+    }
+
+    bool first = true;
+
+    foreach (QString a, args) {
+        QString line;
+        if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) {
+            line = "*." + a;
+        } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) {
+            // Doesn't seem to be possible to do this with a glob,
+            // because the glob is always unanchored and there is no
+            // equivalent of ^ to anchor it
+            line = "re:^" + regexEscape(a) + "$";
+        } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) {
+            line = a;
+        } else if (itype == HgIgnoreDialog::IgnoreWholeDirectory) {
+            line = "re:^" + regexEscape(a) + "/";
+        }
+        if (line != "") {
+            out << line << endl;
+            if (!first) info += "<br>";
+            first = false;
+            info += xmlEncode(line);
+        }
+    }
+
+    f.close();
+    
+    info += "</code></qt>";
+
+    QMessageBox::information(this, tr("Ignored files"),
+                             info);
+
+    hgRefresh();
+}
+
+void MainWindow::hgUnIgnoreFiles(QStringList)
+{
+    // Not implemented: edit the .hgignore instead
+    hgEditIgnore();
+}
+
+void MainWindow::hgShowIn(QStringList files)
+{
+    foreach (QString file, files)
+    {
+        QStringList args;
+#if defined Q_OS_WIN32
+        // Although the Win32 API is quite happy to have
+        // forward slashes as directory separators, Windows
+        // Explorer is not
+        int last = m_workFolderPath.length() - 1;
+        char c = m_workFolderPath[last].toAscii();
+        if (c == '\\' || c == '/') {
+            m_workFolderPath.chop(1);
+        }
+        file = m_workFolderPath + "\\" + file;
+        file = file.replace('/', '\\');
+        args << "/select," << file;
+        // FIXME: This shouldn't be using a hardcoded path.
+        QProcess::execute("c:/windows/explorer.exe", args);
+#elif defined(Q_OS_MAC)
+        file = m_workFolderPath + "/" + file;
+        args << "--reveal" << file;
+        QProcess::execute("/usr/bin/open", args);
+#endif
+    }
+}
+
+QString MainWindow::getDiffBinaryName()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    return settings.value("extdiffbinary", "").toString();
+}
+
+QString MainWindow::getMergeBinaryName()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    return settings.value("mergebinary", "").toString();
+}
+
+void MainWindow::hgShowSummary()
+{
+    QStringList params;
+    
+    params << "diff" << "--stat";
+
+    m_runner->requestAction(HgAction(ACT_UNCOMMITTED_SUMMARY, m_workFolderPath, params));
+}
+
+void MainWindow::hgFolderDiff()
+{
+    hgDiffFiles(QStringList());
+}
+
+void MainWindow::hgDiffFiles(QStringList files)
+{
+    QString diff = getDiffBinaryName();
+    if (diff == "") return;
+
+    QStringList params;
+
+    // Diff parent against working folder (folder diff)
+
+    params << "--config" << "extensions.extdiff=" << "extdiff";
+    params << "--program" << diff << "--";
+
+    QSettings settings;
+    if (settings.value("multipleDiffInstances", false).toBool()) {
+        foreach (QString file, files) {
+            QStringList p = params;
+            p << file;
+            m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, p));
+        }
+    }
+    else {
+        params << files; // may be none: whole dir
+        m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params));
+    }
+}
+
+void MainWindow::hgDiffToCurrent(QString id)
+{
+    QString diff = getDiffBinaryName();
+    if (diff == "") return;
+
+    QStringList params;
+
+    // Diff given revision against working folder
+
+    params << "--config" << "extensions.extdiff=" << "extdiff";
+    params << "--program" << diff;
+    params << "--rev" << Changeset::hashOf(id);
+
+    m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params));
+}
+
+void MainWindow::hgDiffToParent(QString child, QString parent)
+{
+    QString diff = getDiffBinaryName();
+    if (diff == "") return;
+
+    QStringList params;
+
+    // Diff given revision against parent revision
+
+    params << "--config" << "extensions.extdiff=" << "extdiff";
+    params << "--program" << diff;
+    params << "--rev" << Changeset::hashOf(parent)
+           << "--rev" << Changeset::hashOf(child);
+
+    m_runner->requestAction(HgAction(ACT_CHGSETDIFF, m_workFolderPath, params));
+}
+    
+
+void MainWindow::hgShowSummaryFor(Changeset *cs)
+{
+    QStringList params;
+
+    // This will pick a default parent if there is more than one
+    // (whereas with diff we need to supply one).  But it does need a
+    // bit more parsing
+    params << "log" << "--stat" << "--rev" << Changeset::hashOf(cs->id());
+
+    m_runner->requestAction(HgAction(ACT_DIFF_SUMMARY, m_workFolderPath,
+                                     params, cs));
+}
+
+
+void MainWindow::hgUpdate()
+{
+    QStringList params;
+
+    params << "update";
+    
+    m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params));
+}
+
+
+void MainWindow::hgUpdateToRev(QString id)
+{
+    QStringList params;
+
+    params << "update" << "--rev" << Changeset::hashOf(id) << "--check";
+
+    m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params));
+}
+
+
+void MainWindow::hgRevert()
+{
+    hgRevertFiles(QStringList());
+}
+
+void MainWindow::hgRevertFiles(QStringList files)
+{
+    QStringList params;
+    QString comment;
+    bool all = false;
+
+    QStringList allFiles = m_hgTabs->getAllRevertableFiles();
+    if (files.empty() || files == allFiles) {
+        files = allFiles;
+        all = true;
+    }
+    
+    QString subsetNote;
+    if (!all) {
+        subsetNote = tr("<p><b>Note:</b> you are reverting only the files you have selected, not all of the files that have been changed!");
+    }
+
+    QString rf(tr("Revert files"));
+
+    // Set up params before asking for confirmation, because there is
+    // a failure case here that we would need to report on early
+
+    DEBUG << "hgRevert: m_justMerged = " << m_justMerged << ", m_mergeTargetRevision = " << m_mergeTargetRevision << endl;
+
+    if (m_justMerged) {
+
+        // This is a little fiddly.  The proper way to "revert" the
+        // whole of an uncommitted merge is with "hg update --clean ."
+        // But if the user has selected only some files, we're sort of
+        // promising to revert only those, which means we need to
+        // specify which parent to revert to.  We can only do that if
+        // we have a record of it, which we do if you just did the
+        // merge from within easyhg but don't if you've exited and
+        // restarted, or changed repository, since then.  Hmmm.
+
+        if (all) {
+            params << "update" << "--clean" << ".";
+        } else {
+            if (m_mergeTargetRevision != "") {
+                params << "revert" << "--rev"
+                       << Changeset::hashOf(m_mergeTargetRevision)
+                       << "--" << files;
+            } else {
+                QMessageBox::information
+                    (this, tr("Unable to revert"),
+                     tr("<qt><b>Sorry, unable to revert these files</b><br><br>EasyMercurial can only revert a subset of files during a merge if it still has a record of which parent was the original merge target; that information is no longer available.<br><br>This is a limitation of EasyMercurial.  Consider reverting all files, or using hg revert with a specific revision at the command-line instead.</qt>"));
+                return;
+            }
+        }
+    } else {
+        params << "revert" << "--" << files;
+    }
+
+    if (ConfirmCommentDialog::confirmDangerousFilesAction
+        (this,
+         rf,
+         tr("<h3>%1</h3><p>%2%3").arg(rf)
+         .arg(tr("You are about to <b>revert</b> the following files to their previous committed state.<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed."))
+         .arg(subsetNote),
+         tr("<h3>%1</h3><p>%2%3").arg(rf)
+         .arg(tr("You are about to <b>revert</b> %n file(s).<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed.", "", files.size()))
+         .arg(subsetNote),
+         files,
+         tr("Re&vert"))) {
+
+        m_lastRevertedFiles = files;
+        
+        m_runner->requestAction(HgAction(ACT_REVERT, m_workFolderPath, params));
+    }
+}
+
+
+void MainWindow::hgRenameFiles(QStringList files)
+{
+    QString renameTo;
+
+    QString file;
+    if (files.empty()) return;
+    file = files[0];
+
+    if (ConfirmCommentDialog::confirmAndGetShortComment
+        (this,
+         tr("Rename"),
+         tr("Rename <code>%1</code> to:").arg(xmlEncode(file)),
+         renameTo,
+         tr("Re&name"))) {
+        
+        if (renameTo != "" && renameTo != file) {
+
+            QStringList params;
+
+            params << "rename" << "--" << file << renameTo;
+
+            m_runner->requestAction(HgAction(ACT_RENAME_FILE, m_workFolderPath, params));
+        }
+    }
+}
+
+
+void MainWindow::hgCopyFiles(QStringList files)
+{
+    QString copyTo;
+
+    QString file;
+    if (files.empty()) return;
+    file = files[0];
+
+    if (ConfirmCommentDialog::confirmAndGetShortComment
+        (this,
+         tr("Copy"),
+         tr("Copy <code>%1</code> to:").arg(xmlEncode(file)),
+         copyTo,
+         tr("Co&py"))) {
+        
+        if (copyTo != "" && copyTo != file) {
+
+            QStringList params;
+
+            params << "copy" << "--" << file << copyTo;
+
+            m_runner->requestAction(HgAction(ACT_COPY_FILE, m_workFolderPath, params));
+        }
+    }
+}
+
+
+void MainWindow::hgMarkFilesResolved(QStringList files)
+{
+    QStringList params;
+
+    params << "resolve" << "--mark";
+
+    if (files.empty()) {
+        params << "--all";
+    } else {
+        params << "--" << files;
+    }
+
+    m_runner->requestAction(HgAction(ACT_RESOLVE_MARK, m_workFolderPath, params));
+}
+
+
+void MainWindow::hgRedoMerge()
+{
+    hgRedoFileMerges(QStringList());
+}
+
+
+void MainWindow::hgRedoFileMerges(QStringList files)
+{
+    QStringList params;
+
+    params << "resolve";
+
+    QString merge = getMergeBinaryName();
+    if (merge != "") {
+        params << "--tool" << merge;
+    }
+
+    if (files.empty()) {
+        params << "--all";
+    } else {
+        params << "--" << files;
+    }
+
+    if (m_currentParents.size() == 1) {
+        m_mergeTargetRevision = m_currentParents[0]->id();
+    }
+
+    m_runner->requestAction(HgAction(ACT_RETRY_MERGE, m_workFolderPath, params));
+
+    m_mergeCommitComment = tr("Merge");
+}
+    
+
+void MainWindow::hgMerge()
+{
+    if (m_hgTabs->canResolve()) {
+        hgRedoMerge();
+        return;
+    }
+
+    QStringList params;
+
+    params << "merge";
+
+    QString merge = getMergeBinaryName();
+    if (merge != "") {
+        params << "--tool" << merge;
+    }
+
+    if (m_currentParents.size() == 1) {
+        m_mergeTargetRevision = m_currentParents[0]->id();
+    }
+
+    m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params));
+
+    m_mergeCommitComment = tr("Merge");
+}
+
+
+void MainWindow::hgMergeFrom(QString id)
+{
+    QStringList params;
+
+    params << "merge";
+    params << "--rev" << Changeset::hashOf(id);
+
+    QString merge = getMergeBinaryName();
+    if (merge != "") {
+        params << "--tool" << merge;
+    }
+    
+    if (m_currentParents.size() == 1) {
+        m_mergeTargetRevision = m_currentParents[0]->id();
+    }
+
+    m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params));
+
+    m_mergeCommitComment = "";
+
+    foreach (Changeset *cs, m_currentHeads) {
+        if (cs->id() == id && !cs->isOnBranch(m_currentBranch)) {
+            if (cs->branch() == "" || cs->branch() == "default") {
+                m_mergeCommitComment = tr("Merge from the default branch");
+            } else {
+                m_mergeCommitComment = tr("Merge from branch \"%1\"").arg(cs->branch());
+            }
+        }
+    }
+
+    if (m_mergeCommitComment == "") {
+        m_mergeCommitComment = tr("Merge from %1").arg(id);
+    }
+}
+
+
+void MainWindow::hgCloneFromRemote()
+{
+    QStringList params;
+
+    if (!QDir(m_workFolderPath).exists()) {
+        if (!QDir().mkpath(m_workFolderPath)) {
+            DEBUG << "hgCloneFromRemote: Failed to create target path "
+                  << m_workFolderPath << endl;
+            QMessageBox::critical
+                (this, tr("Could not create target folder"),
+                 tr("<qt><b>Could not create target folder</b><br><br>The local target folder \"%1\" does not exist<br>and could not be created.</qt>").arg(xmlEncode(m_workFolderPath)));
+            m_workFolderPath = "";
+            return;
+        }
+    }
+
+    params << "clone" << m_remoteRepoPath << m_workFolderPath;
+    
+    updateWorkFolderAndRepoNames();
+    m_hgTabs->updateWorkFolderFileList("");
+    m_hgTabs->clearAll();
+
+    m_runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, m_workFolderPath, params));
+}
+
+void MainWindow::hgInit()
+{
+    QStringList params;
+
+    params << "init";
+    params << m_workFolderPath;
+
+    m_runner->requestAction(HgAction(ACT_INIT, m_workFolderPath, params));
+}
+
+void MainWindow::hgIncoming()
+{
+    QStringList params;
+
+    params << "incoming" << "--newest-first" << m_remoteRepoPath;
+    params << "--template" << Changeset::getLogTemplate();
+
+    m_runner->requestAction(HgAction(ACT_INCOMING, m_workFolderPath, params));
+}
+
+void MainWindow::hgPull()
+{
+    if (ConfirmCommentDialog::confirm
+        (this, tr("Confirm pull"),
+         tr("<qt><h3>Pull from remote repository?</h3></qt>"),
+         tr("<qt><p>You are about to pull changes from the remote repository at <code>%1</code>.</p></qt>").arg(xmlEncode(m_remoteRepoPath)),
+         tr("&Pull"))) {
+
+        QStringList params;
+        params << "pull" << m_remoteRepoPath;
+        m_runner->requestAction(HgAction(ACT_PULL, m_workFolderPath, params));
+    }
+}
+
+void MainWindow::hgPush()
+{
+    if (m_remoteRepoPath.isEmpty()) {
+        changeRemoteRepo(true);
+        if (m_remoteRepoPath.isEmpty()) return;
+    }
+
+    QString uncommittedNote;
+    if (m_hgTabs->canCommit()) {
+        uncommittedNote = tr("<p><b>Note:</b> You have uncommitted changes.  If you want to push these changes to the remote repository, you need to commit them first.");
+    }
+
+    if (ConfirmCommentDialog::confirm
+        (this, tr("Confirm push"),
+         tr("<qt><h3>Push to remote repository?</h3></qt>"),
+         tr("<qt><p>You are about to push your commits to the remote repository at <code>%1</code>.</p>%2</qt>").arg(xmlEncode(m_remoteRepoPath)).arg(uncommittedNote),
+         tr("&Push"))) {
+
+        QStringList params;
+        params << "push" << "--new-branch" << m_remoteRepoPath;
+        m_runner->requestAction(HgAction(ACT_PUSH, m_workFolderPath, params));
+    }
+}
+
+QStringList MainWindow::listAllUpIpV4Addresses()
+{
+    QStringList ret;
+    QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
+
+    for (int i = 0; i < ifaces.count(); i++) {
+        QNetworkInterface iface = ifaces.at(i);
+        if (iface.flags().testFlag(QNetworkInterface::IsUp)
+            && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
+            for (int j=0; j<iface.addressEntries().count(); j++) {
+                QHostAddress tmp = iface.addressEntries().at(j).ip();
+                if (QAbstractSocket::IPv4Protocol == tmp.protocol()) {
+                    ret.push_back(tmp.toString());
+                }
+            }
+        }
+    }
+    return ret;
+}
+
+void MainWindow::clearState()
+{
+    DEBUG << "MainWindow::clearState" << endl;
+    foreach (Changeset *cs, m_currentParents) delete cs;
+    m_currentParents.clear();
+    foreach (Changeset *cs, m_currentHeads) delete cs;
+    m_currentHeads.clear();
+    m_closedHeadIds.clear();
+    m_currentBranch = "";
+    m_lastStatOutput = "";
+    m_lastRevertedFiles.clear();
+    m_mergeTargetRevision = "";
+    m_mergeCommitComment = "";
+    m_stateUnknown = true;
+    m_needNewLog = true;
+}
+
+void MainWindow::hgServe()
+{
+    QStringList params;
+    QString msg;
+
+    QStringList addrs = listAllUpIpV4Addresses();
+
+    if (addrs.empty()) {
+        QMessageBox::critical
+            (this, tr("Serve"), tr("Failed to identify an active IPv4 address"));
+        return;
+    }
+
+    // See #202. We really want to display the port that the server
+    // ends up using -- but we don't get that information until after
+    // it has exited!  However, we can improve the likelihood of
+    // showing the right port by at least checking whether a port is
+    // defined in the hgrc file.
+
+    QFileInfo hgrc(QDir::homePath() + "/.hgrc");
+    QString path;
+    int port = 8000; // the default
+    if (hgrc.exists()) {
+        QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat);
+        s.beginGroup("web");
+        int p = s.value("port").toInt();
+        if (p) port = p;
+    }
+
+    QTextStream ts(&msg);
+    ts << QString("<qt><h3>%1</h3><p>%2</p>")
+        .arg(tr("Sharing Repository"))
+        .arg(tr("Your local repository is now being made temporarily available via HTTP for workgroup access."));
+    if (addrs.size() > 1) {
+        ts << QString("<p>%3</p>")
+            .arg(tr("Users who have network access to your computer can now clone your repository, by using one of the following URLs as a remote location:"));
+    } else {
+        ts << QString("<p>%3</p>")
+            .arg(tr("Users who have network access to your computer can now clone your repository, by using the following URL as a remote location:"));
+    }
+    foreach (QString addr, addrs) {
+        ts << QString("<pre>&nbsp;&nbsp;http://%1:%2</pre>").arg(xmlEncode(addr)).arg(port);
+    }
+    ts << tr("<p>Press Close to terminate this server, end remote access, and return.</p>");
+    ts.flush();
+             
+    params << "serve";
+
+    m_runner->requestAction(HgAction(ACT_SERVE, m_workFolderPath, params));
+    
+    QMessageBox::information(this, tr("Share Repository"), msg, QMessageBox::Close);
+
+    m_runner->killCurrentActions();
+}
+
+void MainWindow::startupDialog()
+{
+    StartupDialog *dlg = new StartupDialog(this);
+    if (dlg->exec()) m_firstStart = false;
+    else exit(0);
+}
+
+void MainWindow::open()
+{
+    bool done = false;
+
+    while (!done) {
+
+        MultiChoiceDialog *d = new MultiChoiceDialog
+                               (tr("Open Repository"),
+                                tr("<qt><big>What would you like to open?</big></qt>"),
+                                tr("https://code.soundsoftware.ac.uk/projects/easyhg/wiki/HelpOpenDialog"),
+                                this);
+
+        d->addChoice("remote",
+                     tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"),
+                     tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."),
+                     MultiChoiceDialog::UrlToDirectoryArg);
+
+        d->addChoice("local",
+                     tr("<qt><center><img src=\":images/hglogo-64.png\"><br>Local repository</center></qt>"),
+                     tr("Open an existing local Mercurial repository."),
+                     MultiChoiceDialog::DirectoryArg);
+
+        d->addChoice("init",
+                     tr("<qt><center><img src=\":images/hdd_unmount-64.png\"><br>File folder</center></qt>"),
+                     tr("Open a local folder, by creating a Mercurial repository in it."),
+                     MultiChoiceDialog::DirectoryArg);
+
+        QSettings settings;
+        settings.beginGroup("");
+        QString lastChoice = settings.value("lastopentype", "remote").toString();
+        if (lastChoice != "local" &&
+            lastChoice != "remote" &&
+            lastChoice != "init") {
+            lastChoice = "remote";
+        }
+
+        d->setCurrentChoice(lastChoice);
+
+        if (d->exec() == QDialog::Accepted) {
+
+            QString choice = d->getCurrentChoice();
+            settings.setValue("lastopentype", choice);
+
+            QString arg = d->getArgument().trimmed();
+
+            bool result = false;
+
+            if (choice == "local") {
+                result = openLocal(arg);
+            } else if (choice == "remote") {
+                result = openRemote(arg, d->getAdditionalArgument().trimmed());
+            } else if (choice == "init") {
+                result = openInit(arg);
+            }
+
+            if (result) {
+                enableDisableActions();
+                clearState();
+                hgQueryPaths();
+                done = true;
+            }
+
+        } else {
+
+            // cancelled
+            done = true;
+        }
+
+        delete d;
+    }
+}
+
+void MainWindow::recentMenuActivated()
+{
+    QAction *a = qobject_cast<QAction *>(sender());
+    if (!a) return;
+    QString local = a->text();
+    open(local);
+}
+
+void MainWindow::changeRemoteRepo()
+{
+    changeRemoteRepo(false);
+}
+
+void MainWindow::changeRemoteRepo(bool initial)
+{
+    // This will involve rewriting the local .hgrc
+
+    QDir hgDir(m_workFolderPath + "/.hg");
+    if (!hgDir.exists()) {
+        //!!! visible error!
+        return;
+    }
+
+    QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc");
+    if (hgrc.exists() && !hgrc.isWritable()) {
+        //!!! visible error!
+        return;
+    }
+
+    MultiChoiceDialog *d = new MultiChoiceDialog
+        (tr("Set Remote Location"),
+         tr("<qt><big>Set the remote location</big></qt>"),
+         "",
+         this);
+
+    QString explanation;
+    if (initial) {
+        explanation = tr("Provide a remote URL to use when pushing from, or pulling to, the local<br>repository <code>%1</code>.<br>This will be the default for subsequent pushes and pulls.<br>You can change it using &ldquo;Set Remote Location&rdquo; on the File menu.").arg(m_workFolderPath);
+    } else {
+        explanation = tr("Provide a new remote URL to use when pushing from, or pulling to, the local<br>repository <code>%1</code>.").arg(m_workFolderPath);
+    }
+
+    d->addChoice("remote",
+                 tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"),
+                 explanation,
+                 MultiChoiceDialog::UrlArg,
+                 true); // default empty
+
+    if (d->exec() == QDialog::Accepted) {
+
+        // New block to ensure QSettings is deleted before
+        // hgQueryPaths called.  NB use of absoluteFilePath instead of
+        // canonicalFilePath, which would fail if the file did not yet
+        // exist
+
+        {
+            QSettings s(hgrc.absoluteFilePath(), QSettings::IniFormat);
+            s.beginGroup("paths");
+            s.setValue("default", d->getArgument());
+        }
+
+        m_stateUnknown = true;
+        hgQueryPaths();
+    }
+
+    delete d;
+}
+
+void MainWindow::open(QString local)
+{
+    if (openLocal(local)) {
+        enableDisableActions();
+        clearState();
+        hgQueryPaths();
+    }
+}
+
+bool MainWindow::complainAboutFilePath(QString arg)
+{    
+    QMessageBox::critical
+        (this, tr("File chosen"),
+         tr("<qt><b>Folder required</b><br><br>You asked to open \"%1\".<br>This is a file; to open a repository, you need to choose a folder.</qt>").arg(xmlEncode(arg)));
+    return false;
+}
+
+bool MainWindow::askAboutUnknownFolder(QString arg)
+{    
+    bool result = (QMessageBox::question
+                   (this, tr("Path does not exist"),
+                    tr("<qt><b>Path does not exist: create it?</b><br><br>You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.<br><br>Would you like to create the parent folder as well?</qt>").arg(xmlEncode(arg)),
+                    QMessageBox::Ok | QMessageBox::Cancel,
+                    QMessageBox::Cancel)
+                   == QMessageBox::Ok);
+    if (result) {
+        QDir dir(arg);
+        dir.cdUp();
+        if (!dir.mkpath(dir.absolutePath())) {
+            QMessageBox::critical
+                (this, tr("Failed to create folder"),
+                 tr("<qt><b>Failed to create folder</b><br><br>Sorry, the path for the parent folder \"%1\" could not be created.</qt>").arg(dir.absolutePath()));
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+
+bool MainWindow::complainAboutUnknownFolder(QString arg)
+{    
+    QMessageBox::critical
+        (this, tr("Folder does not exist"),
+         tr("<qt><b>Folder does not exist</b><br><br>You asked to open \"%1\".<br>This folder does not exist, and it cannot be created because its parent does not exist either.</qt>").arg(xmlEncode(arg)));
+    return false;
+}
+
+bool MainWindow::complainAboutInitInRepo(QString arg)
+{
+    QMessageBox::critical
+        (this, tr("Path is in existing repository"),
+         tr("<qt><b>Path is in an existing repository</b><br><br>You asked to initialise a repository at \"%1\".<br>This path is already inside an existing repository.</qt>").arg(xmlEncode(arg)));
+    return false;
+}
+
+bool MainWindow::complainAboutInitFile(QString arg)
+{
+    QMessageBox::critical
+        (this, tr("Path is a file"),
+         tr("<qt><b>Path is a file</b><br><br>You asked to initialise a repository at \"%1\".<br>This is an existing file; it is only possible to initialise in folders.</qt>").arg(xmlEncode(arg)));
+    return false;
+}
+
+bool MainWindow::complainAboutCloneToExisting(QString arg)
+{
+    QMessageBox::critical
+        (this, tr("Path is in existing repository"),
+         tr("<qt><b>Local path is in an existing repository</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is already inside an existing repository.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg)));
+    return false;
+}
+
+bool MainWindow::complainAboutCloneToFile(QString arg)
+{
+    QMessageBox::critical
+        (this, tr("Path is a file"),
+         tr("<qt><b>Local path is a file</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is an existing file.<br>Please provide a new folder name for the local repository.</qt>").arg(xmlEncode(arg)));
+    return false;
+}
+
+QString MainWindow::complainAboutCloneToExistingFolder(QString arg, QString remote)
+{
+    // If the directory "arg" exists but is empty, then we accept it.
+
+    // If the directory "arg" exists and is non-empty, but "arg" plus
+    // the last path component of "remote" does not exist, then offer
+    // the latter as an alternative path.
+
+    QString offer;
+
+    QDir d(arg);
+
+    if (d.exists()) {
+
+        if (d.entryList(QDir::Dirs | QDir::Files |
+                        QDir::NoDotAndDotDot |
+                        QDir::Hidden | QDir::System).empty()) {
+            // directory is empty; accept it
+            return arg;
+        }
+
+        if (QRegExp("^\\w+://").indexIn(remote) >= 0) {
+            QString rpath = QUrl(remote).path();
+            if (rpath != "") {
+                rpath = QDir(rpath).dirName();
+                if (rpath != "" && !d.exists(rpath)) {
+                    offer = d.filePath(rpath);
+                }
+            }
+        }
+    }
+
+    if (offer != "") {
+        bool result = (QMessageBox::question
+                       (this, tr("Folder exists"),
+                        tr("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.<br><br>Would you like to create the new folder \"%2\" instead?</qt>")
+                        .arg(xmlEncode(arg)).arg(xmlEncode(offer)),
+                        QMessageBox::Ok | QMessageBox::Cancel,
+                        QMessageBox::Cancel)
+                       == QMessageBox::Ok);
+        if (result) return offer;
+        else return "";
+    }
+
+    QMessageBox::critical
+        (this, tr("Folder exists"),
+         tr("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this file or folder already exists and so cannot be cloned to.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg)));
+    return "";
+}
+
+bool MainWindow::askToOpenParentRepo(QString arg, QString parent)
+{
+    return (QMessageBox::question
+            (this, tr("Path is inside a repository"),
+             tr("<qt><b>Open the repository that contains this path?</b><br><br>You asked to open \"%1\".<br>This is not the root folder of a repository.<br>But it is inside a repository, whose root is at \"%2\". <br><br>Would you like to open that repository instead?</qt>")
+             .arg(xmlEncode(arg)).arg(xmlEncode(parent)),
+             QMessageBox::Ok | QMessageBox::Cancel,
+             QMessageBox::Ok)
+            == QMessageBox::Ok);
+}
+
+bool MainWindow::askToInitExisting(QString arg)
+{
+    return (QMessageBox::question
+            (this, tr("Folder has no repository"),
+             tr("<qt><b>Initialise a repository here?</b><br><br>You asked to open \"%1\".<br>This folder is not a Mercurial working copy.<br><br>Would you like to initialise a repository here?</qt>")
+             .arg(xmlEncode(arg)),
+             QMessageBox::Ok | QMessageBox::Cancel,
+             QMessageBox::Ok)
+            == QMessageBox::Ok);
+}
+
+bool MainWindow::askToInitNew(QString arg)
+{
+    return (QMessageBox::question
+            (this, tr("Folder does not exist"),
+             tr("<qt><b>Initialise a new repository?</b><br><br>You asked to open \"%1\".<br>This folder does not yet exist.<br><br>Would you like to create the folder and initialise a new empty repository in it?</qt>")
+             .arg(xmlEncode(arg)),
+             QMessageBox::Ok | QMessageBox::Cancel,
+             QMessageBox::Ok)
+            == QMessageBox::Ok);
+}
+
+bool MainWindow::askToOpenInsteadOfInit(QString arg)
+{
+    return (QMessageBox::question
+            (this, tr("Repository exists"),
+             tr("<qt><b>Open existing repository?</b><br><br>You asked to initialise a new repository at \"%1\".<br>This folder already contains a repository.  Would you like to open it?</qt>")
+             .arg(xmlEncode(arg)),
+             QMessageBox::Ok | QMessageBox::Cancel,
+             QMessageBox::Ok)
+            == QMessageBox::Ok);
+}
+
+bool MainWindow::openLocal(QString local)
+{
+    DEBUG << "open " << local << endl;
+
+    FolderStatus status = getFolderStatus(local);
+    QString containing = getContainingRepoFolder(local);
+
+    switch (status) {
+
+    case FolderHasRepo:
+        // fine
+        break;
+
+    case FolderExists:
+        if (containing != "") {
+            if (!askToOpenParentRepo(local, containing)) return false;
+            local = containing;
+        } else {
+            //!!! No -- this is likely to happen far more by accident
+            // than because the user actually wanted to init something.
+            // Don't ask, just politely reject.
+            if (!askToInitExisting(local)) return false;
+            return openInit(local);
+        }
+        break;
+
+    case FolderParentExists:
+        if (containing != "") {
+            if (!askToOpenParentRepo(local, containing)) return false;
+            local = containing;
+        } else {
+            if (!askToInitNew(local)) return false;
+            return openInit(local);
+        }
+        break;
+
+    case FolderUnknown:
+        if (containing != "") {
+            if (!askToOpenParentRepo(local, containing)) return false;
+            local = containing;
+        } else {
+            return complainAboutUnknownFolder(local);
+        }
+        break;
+        
+    case FolderIsFile:
+        return complainAboutFilePath(local);
+    }
+
+    m_workFolderPath = local;
+    m_remoteRepoPath = "";
+    return true;
+}    
+
+bool MainWindow::openRemote(QString remote, QString local)
+{
+    DEBUG << "clone " << remote << " to " << local << endl;
+
+    FolderStatus status = getFolderStatus(local);
+    QString containing = getContainingRepoFolder(local);
+
+    DEBUG << "status = " << status << ", containing = " << containing << endl;
+
+    if (status == FolderHasRepo || containing != "") {
+        return complainAboutCloneToExisting(local);
+    }
+
+    if (status == FolderIsFile) {
+        return complainAboutCloneToFile(local);
+    }
+
+    if (status == FolderUnknown) {
+        if (!askAboutUnknownFolder(local)) {
+            return false;
+        }
+    }
+
+    if (status == FolderExists) {
+        local = complainAboutCloneToExistingFolder(local, remote);
+        if (local == "") return false;
+    }
+
+    m_workFolderPath = local;
+    m_remoteRepoPath = remote;
+    hgCloneFromRemote();
+
+    return true;
+}
+
+bool MainWindow::openInit(QString local)
+{
+    DEBUG << "openInit " << local << endl;
+
+    FolderStatus status = getFolderStatus(local);
+    QString containing = getContainingRepoFolder(local);
+
+    DEBUG << "status = " << status << ", containing = " << containing << endl;
+
+    if (status == FolderHasRepo) {
+        if (!askToOpenInsteadOfInit(local)) return false;
+        return openLocal(local);
+    }
+
+    if (containing != "") {
+        return complainAboutInitInRepo(local);
+    }
+
+    if (status == FolderIsFile) {
+        return complainAboutInitFile(local);
+    }
+
+    if (status == FolderUnknown) {
+        return complainAboutUnknownFolder(local);
+    }
+
+    m_workFolderPath = local;
+    m_remoteRepoPath = "";
+    hgInit();
+    return true;
+}
+
+void MainWindow::settings()
+{
+    settings(SettingsDialog::PersonalDetailsTab);
+}
+
+void MainWindow::settings(SettingsDialog::Tab tab)
+{
+    SettingsDialog *settingsDlg = new SettingsDialog(this);
+    settingsDlg->setCurrentTab(tab);
+    settingsDlg->exec();
+
+    if (settingsDlg->presentationChanged()) {
+        m_hgTabs->updateFileStates();
+        updateToolBarStyle();
+        hgRefresh();
+    }
+}
+
+void MainWindow::updateFsWatcher()
+{
+    m_fsWatcher->setWorkDirPath(m_workFolderPath);
+    m_fsWatcher->setTrackedFilePaths(m_hgTabs->getFileStates().trackedFiles());
+}
+
+void MainWindow::checkFilesystem()
+{
+    DEBUG << "MainWindow::checkFilesystem" << endl;
+    if (!m_commandSequenceInProgress) {
+        if (!m_fsWatcher->getChangedPaths(m_fsWatcherToken).empty()) {
+            hgRefresh();
+            return;
+        }
+        updateFsWatcher();
+    }
+}
+
+QString MainWindow::format1(QString head)
+{
+    return QString("<qt><h3>%1</h3></qt>").arg(head);
+}    
+
+QString MainWindow::format3(QString head, QString intro, QString code)
+{
+    code = xmlEncode(code).replace("\n", "<br>")
+#ifndef Q_OS_WIN32
+           // The hard hyphen comes out funny on Windows
+           .replace("-", "&#8209;")
+#endif
+           .replace(" ", "&nbsp;");
+    if (intro == "") {
+        return QString("<qt><h3>%1</h3><p><code>%2</code></p>")
+            .arg(head).arg(code);
+    } else if (code == "") {
+        return QString("<qt><h3>%1</h3><p>%2</p>")
+            .arg(head).arg(intro);
+    } else {
+        return QString("<qt><h3>%1</h3><p>%2</p><p><code>%3</code></p>")
+            .arg(head).arg(intro).arg(code);
+    }
+}
+
+void MainWindow::showIncoming(QString output)
+{
+    m_runner->hide();
+    IncomingDialog *d = new IncomingDialog(this, output);
+    d->exec();
+    delete d;
+}
+
+int MainWindow::extractChangeCount(QString text)
+{
+    QRegExp re("added (\\d+) ch\\w+ with (\\d+) ch\\w+ to (\\d+) f\\w+");
+    if (re.indexIn(text) >= 0) {
+        return re.cap(1).toInt();
+    } else if (text.contains("no changes")) {
+        return 0;
+    } else {
+        return -1; // unknown
+    }
+}
+
+void MainWindow::showPushResult(QString output)
+{
+    QString head;
+    QString report;
+    int n = extractChangeCount(output);
+    if (n > 0) {
+        head = tr("Pushed %n changeset(s)", "", n);
+        report = tr("<qt>Successfully pushed to the remote repository at <code>%1</code>.</qt>").arg(xmlEncode(m_remoteRepoPath));
+    } else if (n == 0) {
+        head = tr("No changes to push");
+        report = tr("The remote repository already contains all changes that have been committed locally.");
+        if (m_hgTabs->canCommit()) {
+            report = tr("%1<p>You do have some uncommitted changes. If you wish to push those to the remote repository, commit them locally first.").arg(report);
+        }            
+    } else {
+        head = tr("Push complete");
+    }
+    m_runner->hide();
+
+    MoreInformationDialog::information(this, tr("Push complete"),
+                                       head, report, output);
+}
+
+void MainWindow::showPullResult(QString output)
+{
+    QString head;
+    QString report;
+    int n = extractChangeCount(output);
+    if (n > 0) {
+        head = tr("Pulled %n changeset(s)", "", n);
+        report = tr("New changes will be highlighted in yellow in the history.");
+    } else if (n == 0) {
+        head = tr("No changes to pull");
+        report = tr("Your local repository already contains all changes found in the remote repository.");
+    } else {
+        head = tr("Pull complete");
+    }
+    m_runner->hide();
+
+    MoreInformationDialog::information(this, tr("Pull complete"),
+                                       head, report, output);
+}
+
+void MainWindow::reportNewRemoteHeads(QString output)
+{
+    bool headsAreLocal = false;
+
+    if (m_currentParents.size() == 1) {
+        int currentBranchActiveHeads = 0;
+        bool parentIsHead = false;
+        Changeset *parent = m_currentParents[0];
+        foreach (Changeset *head, m_activeHeads) {
+            if (head->isOnBranch(m_currentBranch)) {
+                ++currentBranchActiveHeads;
+            }
+            if (parent->id() == head->id()) {
+                parentIsHead = true;
+            }
+        }
+        if (currentBranchActiveHeads == 2 && parentIsHead) {
+            headsAreLocal = true;
+        }
+    }
+
+    if (headsAreLocal) {
+        MoreInformationDialog::warning
+            (this,
+             tr("Push failed"),
+             tr("Push failed"),
+             tr("Your local repository could not be pushed to the remote repository.<br><br>You may need to merge the changes locally first."),
+             output);
+    } else if (m_hgTabs->canCommit() && m_currentParents.size() > 1) {
+        MoreInformationDialog::warning
+            (this,
+             tr("Push failed"),
+             tr("Push failed"),
+             tr("Your local repository could not be pushed to the remote repository.<br><br>You have an uncommitted merge in your local folder.  You probably need to commit it before you push."),
+             output);
+    } else {
+        MoreInformationDialog::warning
+            (this,
+             tr("Push failed"),
+             tr("Push failed"),
+             tr("Your local repository could not be pushed to the remote repository.<br><br>The remote repository may have been changed by someone else since you last pushed. Try pulling and merging their changes into your local repository first."),
+             output);
+    }
+}
+
+void MainWindow::reportAuthFailed(QString output)
+{
+    MoreInformationDialog::warning
+        (this,
+         tr("Authorization failed"),
+         tr("Authorization failed"),
+         tr("You may have entered an incorrect user name or password, or the remote URL may be wrong.<br><br>Or you may lack the necessary permissions on the remote repository.<br><br>Check with the administrator of your remote repository if necessary."),
+         output);
+}
+
+void MainWindow::commandStarting(HgAction)
+{
+    m_commandSequenceInProgress = true;
+}
+
+void MainWindow::commandFailed(HgAction action, QString stdErr, QString stdOut)
+{
+    DEBUG << "MainWindow::commandFailed" << endl;
+
+    m_commandSequenceInProgress = false;
+
+    QString setstr;
+#ifdef Q_OS_MAC
+    setstr = tr("Preferences");
+#else
+    setstr = tr("Settings");
+#endif
+
+    // Some commands we just have to ignore bad return values from,
+    // and some output gets special treatment.
+
+    // Note our fallback case should always be to report a
+    // non-specific error and show the text -- in case output scraping
+    // fails (as it surely will).  Note also that we must force the
+    // locale in order to ensure the output is scrapable; this happens
+    // in HgRunner and may break some system encodings.
+
+    switch(action.action) {
+    case ACT_NONE:
+        // uh huh
+        return;
+    case ACT_TEST_HG:
+        MoreInformationDialog::warning
+            (this,
+             tr("Failed to run Mercurial"),
+             tr("Failed to run Mercurial"),
+             tr("The Mercurial program either could not be found or failed to run.<br><br>Check that the Mercurial program path is correct in %1.").arg(setstr),
+             stdErr);
+        settings(SettingsDialog::PathsTab);
+        return;
+    case ACT_TEST_HG_EXT:
+        MoreInformationDialog::warning
+            (this,
+             tr("Failed to run Mercurial"),
+             tr("Failed to run Mercurial with extension enabled"),
+             tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.<br>This may indicate an installation problem.<br><br>You may be able to continue working if you switch off &ldquo;Use EasyHg Mercurial Extension&rdquo; in %1.  Note that remote repositories that require authentication might not work if you do this.").arg(setstr),
+             stdErr);
+        settings(SettingsDialog::ExtensionsTab);
+        return;
+    case ACT_CLONEFROMREMOTE:
+        // if clone fails, we have no repo
+        m_workFolderPath = "";
+        enableDisableActions();
+        break; // go on to default report
+    case ACT_INCOMING:
+        if (stdErr.contains("authorization failed")) {
+            reportAuthFailed(stdErr);
+            return;
+        } else if (stdErr.contains("entry cancelled")) {
+            // ignore this, user cancelled username or password dialog
+            return;
+        } else {
+            // Incoming returns non-zero code and no stdErr if the
+            // check was successful but there are no changes
+            // pending. This is the only case where we need to remove
+            // warning messages, because it's the only case where a
+            // non-zero code can be returned even though the command
+            // has for our purposes succeeded
+            QStringList lines = stdErr.split(QRegExp("[\\r\\n]+"));
+            QString replaced;
+            foreach (QString line, lines) {
+                line.replace(QRegExp("^.*warning: [^\\n]*"), "");
+                if (line != "") {
+                    replaced += line + "\n";
+                }
+            }
+            if (replaced == "") {
+                showIncoming("");
+                return;
+            }
+        }
+        break; // go on to default report
+    case ACT_PULL:
+        if (stdErr.contains("authorization failed")) {
+            reportAuthFailed(stdErr);
+            return;
+        } else if (stdErr.contains("entry cancelled")) {
+            // ignore this, user cancelled username or password dialog
+            return;
+        } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) {
+            // success: hg 2.1 starts returning failure code for empty pull/push
+            m_commandSequenceInProgress = true; // there may be further commands
+            commandCompleted(action, stdOut);
+            return;
+        }
+        break; // go on to default report
+    case ACT_PUSH:
+        if (stdErr.contains("creates new remote head")) {
+            reportNewRemoteHeads(stdErr);
+            return;
+        } else if (stdErr.contains("authorization failed")) {
+            reportAuthFailed(stdErr);
+            return;
+        } else if (stdErr.contains("entry cancelled")) {
+            // ignore this, user cancelled username or password dialog
+            return;
+        } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) {
+            // success: hg 2.1 starts returning failure code for empty pull/push
+            m_commandSequenceInProgress = true; // there may be further commands
+            commandCompleted(action, stdOut);
+            return;
+        }
+        break; // go on to default report
+    case ACT_QUERY_HEADS_ACTIVE:
+    case ACT_QUERY_HEADS:
+        // fails if repo is empty; we don't care (if there's a genuine
+        // problem, something else will fail too).  Pretend it
+        // succeeded, so that any further actions that are contingent
+        // on the success of the heads query get carried out properly.
+        m_commandSequenceInProgress = true; // there may be further commands
+        commandCompleted(action, "");
+        return;
+    case ACT_FOLDERDIFF:
+    case ACT_CHGSETDIFF:
+        // external program, unlikely to be anything useful in stdErr
+        // and some return with failure codes when something as basic
+        // as the user closing the window via the wm happens
+        return;
+    case ACT_MERGE:
+        if (stdErr.contains("working directory ancestor")) {
+            // arguably we should prevent this upfront, but that's
+            // trickier!
+            MoreInformationDialog::information
+                (this, tr("Merge"), tr("Merge has no effect"),
+                 tr("You asked to merge a revision with one of its ancestors.<p>This has no effect, because the ancestor's changes already exist in both revisions."),
+                 stdErr);
+            return;
+        }
+        // else fall through
+    case ACT_RETRY_MERGE:
+        MoreInformationDialog::information
+            (this, tr("Merge"), tr("Merge failed"),
+             tr("Some files were not merged successfully.<p>You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file's right-button menu."),
+             stdErr);
+        m_mergeCommitComment = "";
+        hgQueryPaths();
+        return;
+    case ACT_STAT:
+        break; // go on to default report
+    default:
+        break;
+    }
+
+    QString command = action.executable;
+    if (command == "") command = "hg";
+    foreach (QString arg, action.params) {
+        command += " " + arg;
+    }
+
+    MoreInformationDialog::warning
+        (this,
+         tr("Command failed"),
+         tr("Command failed"),
+         (stdErr == "" ?
+          tr("A Mercurial command failed to run correctly.  This may indicate an installation problem or some other problem with EasyMercurial.") :
+          tr("A Mercurial command failed to run correctly.  This may indicate an installation problem or some other problem with EasyMercurial.<br><br>See &ldquo;More Details&rdquo; for the command output.")),
+         stdErr);
+}
+
+void MainWindow::commandCompleted(HgAction completedAction, QString output)
+{
+//    std::cerr << "commandCompleted: " << completedAction.action << std::endl;
+
+    HGACTIONS action = completedAction.action;
+
+    if (action == ACT_NONE) return;
+
+    output.replace("\r\n", "\n");
+
+    bool headsChanged = false;
+    QStringList oldHeadIds;
+
+    switch (action) {
+
+    case ACT_TEST_HG:
+    {
+        QRegExp versionRE("^Mercurial.*version ([\\d+])\\.([\\d+])");
+        int pos = versionRE.indexIn(output);
+        if (pos >= 0) {
+            int major = versionRE.cap(1).toInt();
+            int minor = versionRE.cap(2).toInt();
+            // We need v1.7 or newer
+            if (major < 1 || (major == 1 && minor < 7)) {
+                MoreInformationDialog::warning
+                    (this,
+                     tr("Newer Mercurial version required"),
+                     tr("Newer Mercurial version required"),
+                     tr("To use EasyMercurial, you should have at least Mercurial v1.7 installed.<br><br>The version found on this system (v%1.%2) does not support all of the features required by EasyMercurial.").arg(major).arg(minor),
+                     output);
+            }
+        }
+        break;
+    }
+
+    case ACT_TEST_HG_EXT:
+        break;
+
+    case ACT_QUERY_PATHS:
+    {
+        DEBUG << "stdout is " << output << endl;
+        LogParser lp(output, "=");
+        LogList ll = lp.parse();
+        DEBUG << ll.size() << " results" << endl;
+        if (!ll.empty()) {
+            m_remoteRepoPath = lp.parse()[0]["default"].trimmed();
+            DEBUG << "Set remote path to " << m_remoteRepoPath << endl;
+        } else {
+            m_remoteRepoPath = "";
+        }
+        MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
+        MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
+        updateWorkFolderAndRepoNames();
+        break;
+    }
+
+    case ACT_QUERY_BRANCH:
+        m_currentBranch = output.trimmed();
+        break;
+
+    case ACT_STAT:
+        m_lastStatOutput = output;
+        break;
+
+    case ACT_RESOLVE_LIST:
+        // This happens on every update, after the stat (above)
+        if (output != "") {
+            // Remove lines beginning with R (they are resolved,
+            // and the file stat parser treats R as removed)
+            QStringList outList = output.split('\n');
+            QStringList winnowed;
+            foreach (QString line, outList) {
+                if (!line.startsWith("R ")) winnowed.push_back(line);
+            }
+            output = winnowed.join("\n");
+        }
+        DEBUG << "m_lastStatOutput = " << m_lastStatOutput << endl;
+        DEBUG << "resolve output = " << output << endl;
+        m_hgTabs->updateWorkFolderFileList(m_lastStatOutput + output);
+        break;
+
+    case ACT_RESOLVE_MARK:
+        m_shouldHgStat = true;
+        break;
+        
+    case ACT_INCOMING:
+        showIncoming(output);
+        break;
+
+    case ACT_ANNOTATE:
+    {
+        AnnotateDialog dialog(this, output);
+        dialog.exec();
+        m_shouldHgStat = true;
+        break;
+    }
+        
+    case ACT_PULL:
+        showPullResult(output);
+        m_shouldHgStat = true;
+        break;
+        
+    case ACT_PUSH:
+        showPushResult(output);
+        break;
+        
+    case ACT_INIT:
+        MultiChoiceDialog::addRecentArgument("init", m_workFolderPath);
+        MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
+        enableDisableActions();
+        m_shouldHgStat = true;
+        break;
+        
+    case ACT_CLONEFROMREMOTE:
+        MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
+        MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
+        MultiChoiceDialog::addRecentArgument("remote", m_workFolderPath, true);
+        MoreInformationDialog::information
+            (this,
+             tr("Clone"),
+             tr("Open successful"),
+             tr("The remote repository was successfully cloned to the local folder <code>%1</code>.").arg(xmlEncode(m_workFolderPath)),
+             output);
+        enableDisableActions();
+        m_shouldHgStat = true;
+        break;
+        
+    case ACT_LOG:
+        m_hgTabs->setNewLog(output);
+        m_needNewLog = false;
+        break;
+        
+    case ACT_LOG_INCREMENTAL:
+        m_hgTabs->addIncrementalLog(output);
+        break;
+        
+    case ACT_QUERY_PARENTS:
+    {
+        foreach (Changeset *cs, m_currentParents) delete cs;
+        m_currentParents = Changeset::parseChangesets(output);
+        QStringList parentIds = Changeset::getIds(m_currentParents);
+        m_hgTabs->setCurrent(parentIds, m_currentBranch);
+    }
+        break;
+        
+    case ACT_QUERY_HEADS_ACTIVE:
+        foreach (Changeset *cs, m_activeHeads) delete cs;
+        m_activeHeads = Changeset::parseChangesets(output);
+        break;
+
+    case ACT_QUERY_HEADS:
+    {
+        oldHeadIds = Changeset::getIds(m_currentHeads);
+        Changesets newHeads = Changeset::parseChangesets(output);
+        QStringList newHeadIds = Changeset::getIds(newHeads);
+        if (oldHeadIds != newHeadIds) {
+            DEBUG << "Heads changed, will prompt an incremental log if appropriate" << endl;
+            DEBUG << "Old heads: " << oldHeadIds.join(",") << endl;
+            DEBUG << "New heads: " << newHeadIds.join(",") << endl;
+            headsChanged = true;
+            foreach (Changeset *cs, m_currentHeads) delete cs;
+            m_currentHeads = newHeads;
+            updateClosedHeads();
+        }
+    }
+        break;
+
+    case ACT_COMMIT:
+        if (m_currentParents.empty()) {
+            // first commit to empty repo
+            m_needNewLog = true;
+        }
+        m_hgTabs->clearSelections();
+        m_justMerged = false;
+        m_shouldHgStat = true;
+        break;
+
+    case ACT_CLOSE_BRANCH:
+        m_hgTabs->clearSelections();
+        m_justMerged = false;
+        m_shouldHgStat = true;
+        break;
+
+    case ACT_REVERT:
+        hgMarkFilesResolved(m_lastRevertedFiles);
+        m_justMerged = false;
+        break;
+        
+    case ACT_REMOVE:
+    case ACT_ADD:
+        m_hgTabs->clearSelections();
+        m_shouldHgStat = true;
+        break;
+
+    case ACT_TAG:
+        m_needNewLog = true;
+        m_shouldHgStat = true;
+        break;
+
+    case ACT_NEW_BRANCH:
+        m_shouldHgStat = true;
+        break;
+
+    case ACT_UNCOMMITTED_SUMMARY:
+        QMessageBox::information(this, tr("Change summary"),
+                                 format3(tr("Summary of uncommitted changes"),
+                                         "",
+                                         output));
+        break;
+
+    case ACT_DIFF_SUMMARY:
+    {
+        // Output has log info first, diff following after a blank line
+        QStringList olist = output.split("\n\n", QString::SkipEmptyParts);
+        if (olist.size() > 1) output = olist[1];
+
+        Changeset *cs = (Changeset *)completedAction.extraData;
+        if (cs) {
+            QMessageBox::information
+                (this, tr("Change summary"),
+                 format3(tr("Summary of changes"),
+                         cs->formatHtml(),
+                         output));
+        } else if (output == "") {
+            // Can happen, for a merge commit (depending on parent)
+            QMessageBox::information(this, tr("Change summary"),
+                                     format3(tr("Summary of changes"),
+                                             tr("No changes"),
+                                             output));
+        } else {
+            QMessageBox::information(this, tr("Change summary"),
+                                     format3(tr("Summary of changes"),
+                                             "",
+                                             output));
+        }            
+        break;
+    }
+
+    case ACT_FOLDERDIFF:
+    case ACT_CHGSETDIFF:
+    case ACT_SERVE:
+    case ACT_HG_IGNORE:
+        m_shouldHgStat = true;
+        break;
+        
+    case ACT_UPDATE:
+        QMessageBox::information(this, tr("Update"), tr("<qt><h3>Update successful</h3><p>%1</p>").arg(xmlEncode(output)));
+        m_shouldHgStat = true;
+        break;
+        
+    case ACT_MERGE:
+        MoreInformationDialog::information
+            (this, tr("Merge"), tr("Merge successful"),
+             tr("Remember to test and commit the result before making any further changes."),
+             output);
+        m_shouldHgStat = true;
+        m_justMerged = true;
+        break;
+        
+    case ACT_RETRY_MERGE:
+        QMessageBox::information(this, tr("Resolved"),
+                                 tr("<qt><h3>Merge resolved</h3><p>Merge resolved successfully.<br>Remember to test and commit the result before making any further changes.</p>"));
+        m_shouldHgStat = true;
+        m_justMerged = true;
+        break;
+        
+    default:
+        break;
+    }
+
+    // Sequence when no full log required:
+    //   paths -> branch -> stat -> resolve-list -> heads ->
+    //     incremental-log (only if heads changed) -> parents
+    // 
+    // Sequence when full log required:
+    //   paths -> branch -> stat -> resolve-list -> heads ->
+    //     parents -> log
+    //
+    // Note we want to call enableDisableActions only once, at the end
+    // of whichever sequence is in use.
+
+    bool noMore = false;
+
+    switch (action) {
+
+    case ACT_TEST_HG:
+    {
+        QSettings settings;
+        if (settings.value("useextension", true).toBool()) {
+            hgTestExtension();
+        } else if (m_workFolderPath == "") {
+            open();
+        } else {
+            hgQueryPaths();
+        }
+        break;
+    }
+        
+    case ACT_TEST_HG_EXT:
+        if (m_workFolderPath == "") {
+            open();
+        } else{
+            hgQueryPaths();
+        }
+        break;
+        
+    case ACT_QUERY_PATHS:
+        // NB this call is duplicated in hgQueryPaths
+        hgQueryBranch();
+        break;
+
+    case ACT_QUERY_BRANCH:
+        // NB this call is duplicated in hgQueryBranch
+        hgStat();
+        break;
+        
+    case ACT_STAT:
+        hgResolveList();
+        break;
+        
+    case ACT_RESOLVE_LIST:
+        hgQueryHeadsActive();
+        break;
+
+    case ACT_QUERY_HEADS_ACTIVE:
+        hgQueryHeads();
+        break;
+
+    case ACT_QUERY_HEADS:
+        if (headsChanged && !m_needNewLog) {
+            hgLogIncremental(oldHeadIds);
+        } else {
+            hgQueryParents();
+        }
+        break;
+
+    case ACT_LOG_INCREMENTAL:
+        hgQueryParents();
+        break;
+
+    case ACT_QUERY_PARENTS:
+        if (m_needNewLog) {
+            hgLog();
+        } else {
+            // we're done
+            noMore = true;
+        }
+        break;
+
+    case ACT_LOG:
+        // we're done
+        noMore = true;
+        break;
+
+    default:
+        if (m_shouldHgStat) {
+            m_shouldHgStat = false;
+            hgQueryPaths();
+        } else {
+            noMore = true;
+        }
+        break;
+    }
+
+    if (noMore) {
+        m_commandSequenceInProgress = false;
+        m_stateUnknown = false;
+        enableDisableActions();
+        m_hgTabs->updateHistory();
+        updateRecentMenu();
+        checkFilesystem();
+    }
+}
+
+void MainWindow::commandCancelled(HgAction)
+{
+    // Originally I had this checking whether the cancelled action was
+    // a network one and, if so, calling hgQueryPaths to update the
+    // local view in case it had changed anything. But that doesn't
+    // work properly -- because at this point, although the command
+    // has been cancelled and a kill signal sent, it hasn't actually
+    // exited yet. If we request another command now, it will go on
+    // the stack and be associated with the failed exit forthcoming
+    // from the cancelled command -- giving the user a disturbing
+    // command-failed dialog
+}
+
+void MainWindow::connectActions()
+{
+    connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close()));
+    connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about()));
+    connect(m_helpAct, SIGNAL(triggered()), this, SLOT(help()));
+
+    connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh()));
+    connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove()));
+    connect(m_hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd()));
+    connect(m_hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit()));
+    connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore()));
+    connect(m_hgEditIgnoreAct, SIGNAL(triggered()), this, SLOT(hgEditIgnore()));
+    connect(m_hgFolderDiffAct, SIGNAL(triggered()), this, SLOT(hgFolderDiff()));
+    connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate()));
+    connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert()));
+    connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge()));
+
+    connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings()));
+    connect(m_openAct, SIGNAL(triggered()), this, SLOT(open()));
+    connect(m_changeRemoteRepoAct, SIGNAL(triggered()), this, SLOT(changeRemoteRepo()));
+
+    connect(m_hgIncomingAct, SIGNAL(triggered()), this, SLOT(hgIncoming()));
+    connect(m_hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull()));
+    connect(m_hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush()));
+
+    connect(m_hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe()));
+}
+
+void MainWindow::connectTabsSignals()
+{
+    connect(m_hgTabs, SIGNAL(currentChanged(int)),
+            this, SLOT(enableDisableActions()));
+
+    connect(m_hgTabs, SIGNAL(commit()),
+            this, SLOT(hgCommit()));
+    
+    connect(m_hgTabs, SIGNAL(revert()),
+            this, SLOT(hgRevert()));
+    
+    connect(m_hgTabs, SIGNAL(diffWorkingFolder()),
+            this, SLOT(hgFolderDiff()));
+    
+    connect(m_hgTabs, SIGNAL(showSummary()),
+            this, SLOT(hgShowSummary()));
+    
+    connect(m_hgTabs, SIGNAL(newBranch()),
+            this, SLOT(hgNewBranch()));
+    
+    connect(m_hgTabs, SIGNAL(noBranch()),
+            this, SLOT(hgNoBranch()));
+
+    connect(m_hgTabs, SIGNAL(updateTo(QString)),
+            this, SLOT(hgUpdateToRev(QString)));
+
+    connect(m_hgTabs, SIGNAL(diffToCurrent(QString)),
+            this, SLOT(hgDiffToCurrent(QString)));
+
+    connect(m_hgTabs, SIGNAL(diffToParent(QString, QString)),
+            this, SLOT(hgDiffToParent(QString, QString)));
+
+    connect(m_hgTabs, SIGNAL(showSummary(Changeset *)),
+            this, SLOT(hgShowSummaryFor(Changeset *)));
+
+    connect(m_hgTabs, SIGNAL(mergeFrom(QString)),
+            this, SLOT(hgMergeFrom(QString)));
+
+    connect(m_hgTabs, SIGNAL(newBranch(QString)),
+            this, SLOT(hgNewBranch()));
+
+    connect(m_hgTabs, SIGNAL(closeBranch(QString)),
+            this, SLOT(hgCloseBranch()));
+
+    connect(m_hgTabs, SIGNAL(tag(QString)),
+            this, SLOT(hgTag(QString)));
+
+    connect(m_hgTabs, SIGNAL(annotateFiles(QStringList)),
+            this, SLOT(hgAnnotateFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(diffFiles(QStringList)),
+            this, SLOT(hgDiffFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(commitFiles(QStringList)),
+            this, SLOT(hgCommitFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(revertFiles(QStringList)),
+            this, SLOT(hgRevertFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(renameFiles(QStringList)),
+            this, SLOT(hgRenameFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(copyFiles(QStringList)),
+            this, SLOT(hgCopyFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(addFiles(QStringList)),
+            this, SLOT(hgAddFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(removeFiles(QStringList)),
+            this, SLOT(hgRemoveFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(redoFileMerges(QStringList)),
+            this, SLOT(hgRedoFileMerges(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(markFilesResolved(QStringList)),
+            this, SLOT(hgMarkFilesResolved(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(ignoreFiles(QStringList)),
+            this, SLOT(hgIgnoreFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(unIgnoreFiles(QStringList)),
+            this, SLOT(hgUnIgnoreFiles(QStringList)));
+
+    connect(m_hgTabs, SIGNAL(showIn(QStringList)),
+            this, SLOT(hgShowIn(QStringList)));
+}    
+
+void MainWindow::enableDisableActions()
+{
+    DEBUG << "MainWindow::enableDisableActions" << endl;
+
+    QString dirname = QDir(m_workFolderPath).dirName();
+
+    if (m_workFolderPath != "") { // dirname of "" is ".", so test path instead
+        setWindowTitle(tr("EasyMercurial: %1").arg(dirname));
+    } else {
+        setWindowTitle(tr("EasyMercurial"));
+    }
+
+    //!!! should also do things like set the status texts for the
+    //!!! actions appropriately by context
+
+    QDir localRepoDir;
+    QDir workFolderDir;
+
+    m_remoteRepoActionsEnabled = true;
+    if (m_remoteRepoPath.isEmpty()) {
+        m_remoteRepoActionsEnabled = false;
+    }
+
+    m_localRepoActionsEnabled = true;
+    if (m_workFolderPath.isEmpty()) {
+        m_localRepoActionsEnabled = false;
+    }
+
+    if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) {
+        m_localRepoActionsEnabled = false;
+    }
+
+    if (!localRepoDir.exists(m_workFolderPath + "/.hg")) {
+        m_localRepoActionsEnabled = false;
+    }
+
+    bool haveDiff = false;
+    QSettings settings;
+    settings.beginGroup("Locations");
+    if (settings.value("extdiffbinary", "").toString() != "") {
+        haveDiff = true;
+    }
+    settings.endGroup();
+
+    m_hgTabs->setHaveMerge(m_currentParents.size() == 2);
+
+    m_hgRefreshAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && haveDiff);
+    m_hgRevertAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgAddAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgCommitAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgMergeAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgServeAct->setEnabled(m_localRepoActionsEnabled);
+    m_hgEditIgnoreAct->setEnabled(m_localRepoActionsEnabled);
+
+    DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl;
+    DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl;
+
+    m_hgAddAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canAdd());
+    m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRemove());
+    m_hgCommitAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canCommit());
+    m_hgRevertAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRevert());
+    m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canDiff());
+    m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canIgnore());
+
+    // A default merge makes sense if:
+    //  * there is only one parent (if there are two, we have an uncommitted merge) and
+    //  * there are exactly two heads that have the same branch as the current branch and
+    //  * our parent is one of those heads
+    //
+    // A default update makes sense if:
+    //  * there is only one parent and
+    //  * the parent is not one of the current heads
+
+    bool canMerge = false;
+    bool canUpdate = false;
+    bool haveMerge = false;
+    bool emptyRepo = false;
+    bool noWorkingCopy = false;
+    bool newBranch = false;
+    bool closedBranch = false;
+    int currentBranchActiveHeads = 0;
+
+    if (m_currentParents.size() == 1) {
+        bool parentIsHead = false;
+        bool parentIsActiveHead = false;
+        Changeset *parent = m_currentParents[0];
+        foreach (Changeset *head, m_activeHeads) {
+            if (head->isOnBranch(m_currentBranch)) {
+                ++currentBranchActiveHeads;
+            }
+            if (parent->id() == head->id()) {
+                parentIsActiveHead = parentIsHead = true;
+            }
+        }
+        if (!parentIsActiveHead) {
+            foreach (Changeset *head, m_currentHeads) {
+                if (parent->id() == head->id()) {
+                    parentIsHead = true;
+                }
+            }
+        }
+        if (currentBranchActiveHeads == 2 && parentIsActiveHead) {
+            canMerge = true;
+        }
+        if (currentBranchActiveHeads == 0 && parentIsActiveHead) {
+            // Just created a new branch
+            newBranch = true;
+        }
+        if (!parentIsHead) {
+            canUpdate = true;
+            DEBUG << "parent id = " << parent->id() << endl;
+            DEBUG << " head ids "<<endl;
+            foreach (Changeset *h, m_currentHeads) {
+                DEBUG << "head id = " << h->id() << endl;
+            }
+        } else if (!parentIsActiveHead) {
+            closedBranch = true;
+        }
+        m_justMerged = false;
+    } else if (m_currentParents.size() == 0) {
+        if (m_currentHeads.size() == 0) {
+            // No heads -> empty repo
+            emptyRepo = true;
+        } else {
+            // Heads, but no parents -> no working copy, e.g. we have
+            // just converted this repo but haven't updated in it yet.
+            // Uncommon but confusing; probably merits a special case
+            noWorkingCopy = true;
+            canUpdate = true;
+        }
+        m_justMerged = false;
+    } else {
+        haveMerge = true;
+        m_justMerged = true;
+    }
+
+    m_hgIncomingAct->setEnabled(m_remoteRepoActionsEnabled);
+    m_hgPullAct->setEnabled(m_remoteRepoActionsEnabled);
+    // permit push even if no remote yet; we'll ask for one
+    m_hgPushAct->setEnabled(m_localRepoActionsEnabled && !emptyRepo);
+
+    m_hgMergeAct->setEnabled(m_localRepoActionsEnabled &&
+                             (canMerge || m_hgTabs->canResolve()));
+    m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled &&
+                              (canUpdate && !m_hgTabs->haveChangesToCommit()));
+
+    // Set the state field on the file status widget
+
+    QString branchText;
+    if (m_currentBranch == "" || m_currentBranch == "default") {
+        branchText = tr("the default branch");
+    } else {
+        branchText = tr("branch \"%1\"").arg(m_currentBranch);
+    }
+
+    if (m_stateUnknown) {
+        if (m_workFolderPath == "") {
+            m_workStatus->setState(tr("No repository open"));
+        } else {
+            m_workStatus->setState(tr("(Examining repository)"));
+        }
+    } else if (emptyRepo) {
+        m_workStatus->setState(tr("Nothing committed to this repository yet"));
+    } else if (noWorkingCopy) {
+        m_workStatus->setState(tr("No working copy yet: consider updating"));
+    } else if (canMerge) {
+        m_workStatus->setState(tr("<b>Awaiting merge</b> on %1").arg(branchText));
+    } else if (!m_hgTabs->getAllUnresolvedFiles().empty()) {
+        m_workStatus->setState(tr("Have unresolved files following merge on %1").arg(branchText));
+    } else if (haveMerge) {
+        m_workStatus->setState(tr("Have merged but not yet committed on %1").arg(branchText));
+    } else if (newBranch) {
+        m_workStatus->setState(tr("On %1.  New branch: has not yet been committed").arg(branchText));
+    } else if (closedBranch) {
+        if (canUpdate) {
+            m_workStatus->setState(tr("On a closed branch. Not at the head of the branch"));
+        } else {
+            m_workStatus->setState(tr("At the head of a closed branch"));
+        }
+    } else if (canUpdate) {
+        if (m_hgTabs->haveChangesToCommit()) {
+            // have uncommitted changes
+            m_workStatus->setState(tr("On %1. Not at the head of the branch").arg(branchText));
+        } else {
+            // no uncommitted changes
+            m_workStatus->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText));
+        }
+    } else if (currentBranchActiveHeads > 1) {
+        m_workStatus->setState(tr("At one of %n heads of %1", "", currentBranchActiveHeads).arg(branchText));
+    } else {
+        m_workStatus->setState(tr("At the head of %1").arg(branchText));
+    }
+}
+
+
+void MainWindow::updateClosedHeads()
+{
+    m_closedHeadIds.clear();
+    QSet<QString> activeIds;
+    foreach (Changeset *cs, m_activeHeads) {
+        activeIds.insert(cs->id());
+    }
+    foreach (Changeset *cs, m_currentHeads) {
+        if (!activeIds.contains(cs->id())) {
+            m_closedHeadIds.insert(cs->id());
+        }
+    }
+    m_hgTabs->setClosedHeadIds(m_closedHeadIds);
+}
+
+void MainWindow::updateRecentMenu()
+{
+    m_recentMenu->clear();
+    RecentFiles rf("Recent-local");
+    QStringList recent = rf.getRecent();
+    if (recent.empty()) {
+        QLabel *label = new QLabel(tr("No recent local repositories"));
+        QWidgetAction *wa = new QWidgetAction(m_recentMenu);
+        wa->setDefaultWidget(label);
+        return;
+    }
+    foreach (QString r, recent) {
+        QAction *a = m_recentMenu->addAction(r);
+        connect(a, SIGNAL(triggered()), this, SLOT(recentMenuActivated()));
+    }
+}
+
+void MainWindow::createActions()
+{
+    //File actions
+    m_openAct = new QAction(QIcon(":/images/fileopen.png"), tr("&Open..."), this);
+    m_openAct->setStatusTip(tr("Open a remote repository or an existing local folder"));
+    m_openAct->setShortcut(tr("Ctrl+O"));
+
+    m_changeRemoteRepoAct = new QAction(tr("Set Push and Pull &Location..."), this);
+    m_changeRemoteRepoAct->setStatusTip(tr("Set or change the default URL for pull and push actions from this repository"));
+
+    m_settingsAct = new QAction(QIcon(":/images/settings.png"), tr("&Settings..."), this);
+    m_settingsAct->setStatusTip(tr("View and change application settings"));
+
+#ifdef Q_OS_WIN32
+    m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("E&xit"), this);
+#else 
+    m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("&Quit"), this);
+#endif
+    m_exitAct->setShortcuts(QKeySequence::Quit);
+    m_exitAct->setStatusTip(tr("Exit EasyMercurial"));
+
+    //Repository actions
+    m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Re-Read Working Folder"), this);
+    m_hgRefreshAct->setShortcut(tr("Ctrl+R"));
+    m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder"));
+
+    m_hgIncomingAct = new QAction(QIcon(":/images/incoming.png"), tr("Pre&view Incoming Changes"), this);
+    m_hgIncomingAct->setIconText(tr("Preview"));
+    m_hgIncomingAct->setStatusTip(tr("See what changes are available in the remote repository waiting to be pulled"));
+
+    m_hgPullAct = new QAction(QIcon(":/images/pull.png"), tr("Pu&ll from Remote Repository"), this);
+    m_hgPullAct->setIconText(tr("Pull"));
+    m_hgPullAct->setShortcut(tr("Ctrl+L"));
+    m_hgPullAct->setStatusTip(tr("Pull changes from the remote repository to the local repository"));
+
+    m_hgPushAct = new QAction(QIcon(":/images/push.png"), tr("Pus&h to Remote Repository"), this);
+    m_hgPushAct->setIconText(tr("Push"));
+    m_hgPushAct->setShortcut(tr("Ctrl+H"));
+    m_hgPushAct->setStatusTip(tr("Push changes from the local repository to the remote repository"));
+
+    //Workfolder actions
+    m_hgFolderDiffAct   = new QAction(QIcon(":/images/folderdiff.png"), tr("&Diff"), this);
+    m_hgFolderDiffAct->setIconText(tr("Diff"));
+    m_hgFolderDiffAct->setShortcut(tr("Ctrl+D"));
+    m_hgFolderDiffAct->setStatusTip(tr("See what has changed in the working folder compared with the last committed state"));
+
+    m_hgRevertAct = new QAction(QIcon(":/images/undo.png"), tr("Re&vert"), this);
+    m_hgRevertAct->setStatusTip(tr("Throw away your changes and return to the last committed state"));
+
+    m_hgAddAct = new QAction(QIcon(":/images/add.png"), tr("&Add Files"), this);
+    m_hgAddAct->setIconText(tr("Add"));
+    m_hgAddAct->setShortcut(tr("+"));
+    m_hgAddAct->setStatusTip(tr("Mark the selected files to be added on the next commit"));
+
+    m_hgRemoveAct = new QAction(QIcon(":/images/remove.png"), tr("&Remove Files"), this);
+    m_hgRemoveAct->setIconText(tr("Remove"));
+    m_hgRemoveAct->setShortcut(tr("Del"));
+    m_hgRemoveAct->setStatusTip(tr("Mark the selected files to be removed from version control on the next commit"));
+
+    m_hgIgnoreAct = new QAction(tr("&Ignore Files..."), this);
+    m_hgIgnoreAct->setStatusTip(tr("Add the selected filenames to the ignored list, of files that should never be tracked in this repository"));
+
+    m_hgEditIgnoreAct = new QAction(tr("Edit Ignored List"), this);
+    m_hgEditIgnoreAct->setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial"));
+
+    m_hgUpdateAct = new QAction(QIcon(":/images/update.png"), tr("&Update to Branch Head"), this);
+    m_hgUpdateAct->setIconText(tr("Update"));
+    m_hgUpdateAct->setShortcut(tr("Ctrl+U"));
+    m_hgUpdateAct->setStatusTip(tr("Update the working folder to the head of the current repository branch"));
+
+    m_hgCommitAct = new QAction(QIcon(":/images/commit.png"), tr("&Commit..."), this);
+    m_hgCommitAct->setShortcut(tr("Ctrl+Return"));
+    m_hgCommitAct->setStatusTip(tr("Commit your changes to the local repository"));
+
+    m_hgMergeAct = new QAction(QIcon(":/images/merge.png"), tr("&Merge"), this);
+    m_hgMergeAct->setShortcut(tr("Ctrl+M"));
+    m_hgMergeAct->setStatusTip(tr("Merge the two independent sets of changes in the local repository into the working folder"));
+
+    m_hgServeAct = new QAction(tr("Share Repository"), this);
+    m_hgServeAct->setStatusTip(tr("Serve local repository temporarily via HTTP for workgroup access"));
+
+    //Help actions
+#ifdef Q_OS_MAC
+    m_helpAct = new QAction(tr("EasyMercurial Help"), this);
+#else
+    m_helpAct = new QAction(tr("Help Topics"), this);
+#endif
+    m_helpAct->setShortcuts(QKeySequence::HelpContents);
+    m_aboutAct = new QAction(tr("About EasyMercurial"), this);
+
+    // Miscellaneous
+    QShortcut *clearSelectionsShortcut = new QShortcut(Qt::Key_Escape, this);
+    connect(clearSelectionsShortcut, SIGNAL(activated()),
+            this, SLOT(clearSelections()));
+}
+
+void MainWindow::createMenus()
+{
+#ifdef Q_OS_LINUX
+    // In Ubuntu 14.04 the window's menu bar goes missing entirely if
+    // the user is running any desktop environment other than Unity
+    // (in which the faux single-menubar appears). The user has a
+    // workaround, to remove the appmenu-qt5 package, but that is
+    // awkward and the problem is so severe that it merits disabling
+    // the system menubar integration altogether. Like this:
+    menuBar()->setNativeMenuBar(false);
+#endif
+
+    m_fileMenu = menuBar()->addMenu(tr("&File"));
+
+    m_fileMenu->addAction(m_openAct);
+    m_recentMenu = m_fileMenu->addMenu(tr("Open Re&cent"));
+    m_fileMenu->addAction(m_hgRefreshAct);
+    m_fileMenu->addSeparator();
+    m_fileMenu->addAction(m_hgServeAct);
+    m_fileMenu->addSeparator();
+    m_fileMenu->addAction(m_settingsAct);
+    m_fileMenu->addSeparator();
+    m_fileMenu->addAction(m_exitAct);
+
+    QMenu *workMenu;
+    workMenu = menuBar()->addMenu(tr("&Work"));
+    workMenu->addAction(m_hgFolderDiffAct);
+    workMenu->addSeparator();
+    workMenu->addAction(m_hgUpdateAct);
+    workMenu->addAction(m_hgCommitAct);
+    workMenu->addAction(m_hgMergeAct);
+    workMenu->addSeparator();
+    workMenu->addAction(m_hgAddAct);
+    workMenu->addAction(m_hgRemoveAct);
+    workMenu->addSeparator();
+    workMenu->addAction(m_hgIgnoreAct);
+    workMenu->addAction(m_hgEditIgnoreAct);
+    workMenu->addSeparator();
+    workMenu->addAction(m_hgRevertAct);
+
+    QMenu *remoteMenu;
+    remoteMenu = menuBar()->addMenu(tr("&Remote"));
+    remoteMenu->addAction(m_hgIncomingAct);
+    remoteMenu->addSeparator();
+    remoteMenu->addAction(m_changeRemoteRepoAct);
+    remoteMenu->addAction(m_hgPullAct);
+    remoteMenu->addAction(m_hgPushAct);
+
+    m_helpMenu = menuBar()->addMenu(tr("&Help"));
+    m_helpMenu->addAction(m_helpAct);
+    m_helpMenu->addAction(m_aboutAct);
+}
+
+void MainWindow::createToolBars()
+{
+    int sz = 32;
+
+    QString spacerBefore, spacerAfter;
+
+    spacerBefore = spacerAfter = " ";
+
+#ifdef Q_OS_MAC
+    spacerAfter = "";
+#endif
+
+#ifdef Q_OS_WIN32
+    spacerBefore = spacerAfter = "  ";
+#endif
+
+    m_repoToolBar = addToolBar(tr("Remote"));
+    m_repoToolBar->setIconSize(QSize(sz, sz));
+    if (spacerBefore != "") {
+        m_repoToolBar->addWidget(new QLabel(spacerBefore));
+    }
+    m_repoToolBar->addAction(m_openAct);
+    if (spacerAfter != "") {
+        m_repoToolBar->addWidget(new QLabel(spacerAfter));
+    }
+    m_repoToolBar->addSeparator();
+    m_repoToolBar->addAction(m_hgIncomingAct);
+    m_repoToolBar->addAction(m_hgPullAct);
+    m_repoToolBar->addAction(m_hgPushAct);
+    m_repoToolBar->setMovable(false);
+
+    m_workFolderToolBar = new QToolBar(tr("Work"));
+    addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar);
+    m_workFolderToolBar->setIconSize(QSize(sz, sz));
+
+    QWidget *w = new QWidget;
+    w->setFixedHeight(6);
+    m_workFolderToolBar->addWidget(w);
+
+    m_workFolderToolBar->addAction(m_hgFolderDiffAct);
+    m_workFolderToolBar->addSeparator();
+    m_workFolderToolBar->addAction(m_hgRevertAct);
+    m_workFolderToolBar->addAction(m_hgUpdateAct);
+    m_workFolderToolBar->addAction(m_hgCommitAct);
+    m_workFolderToolBar->addAction(m_hgMergeAct);
+    m_workFolderToolBar->addSeparator();
+    m_workFolderToolBar->addAction(m_hgAddAct);
+    m_workFolderToolBar->addAction(m_hgRemoveAct);
+    m_workFolderToolBar->setMovable(false);
+
+    updateToolBarStyle();
+}
+
+void MainWindow::updateToolBarStyle()
+{
+    QSettings settings;
+    settings.beginGroup("Presentation");
+    bool showText = settings.value("showiconlabels", true).toBool();
+    settings.endGroup();
+    
+    foreach (QToolButton *tb, findChildren<QToolButton *>()) {
+        tb->setToolButtonStyle(showText ?
+                               Qt::ToolButtonTextUnderIcon :
+                               Qt::ToolButtonIconOnly);
+    }
+}    
+
+void MainWindow::updateWorkFolderAndRepoNames()
+{
+    m_hgTabs->setLocalPath(m_workFolderPath);
+
+    m_workStatus->setLocalPath(m_workFolderPath);
+    m_workStatus->setRemoteURL(m_remoteRepoPath);
+}
+
+void MainWindow::createStatusBar()
+{
+    statusBar()->showMessage(tr("Ready"));
+}
+
+void MainWindow::readSettings()
+{
+    QDir workFolder;
+
+    QSettings settings;
+
+    m_workFolderPath = settings.value("workfolderpath", "").toString();
+    if (!workFolder.exists(m_workFolderPath)) {
+        m_workFolderPath = "";
+    }
+
+    QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
+    QSize size = settings.value("size", QSize(550, 550)).toSize();
+    m_firstStart = settings.value("firststart", QVariant(true)).toBool();
+
+    resize(size);
+    move(pos);
+}
+
+void MainWindow::writeSettings()
+{
+    QSettings settings;
+    settings.setValue("pos", pos());
+    settings.setValue("size", size());
+    settings.setValue("remoterepopath", m_remoteRepoPath);
+    settings.setValue("workfolderpath", m_workFolderPath);
+    settings.setValue("firststart", m_firstStart);
+}
+
+void MainWindow::newerVersionAvailable(QString version)
+{
+    QSettings settings;
+    settings.beginGroup("NewerVersionWarning");
+    QString tag = QString("version-%1-available-show").arg(version);
+    if (settings.value(tag, true).toBool()) {
+        QString title(tr("Newer version available"));
+        QString text(tr("<h3>Newer version available</h3><p>You are using version %1 of EasyMercurial, but version %3 is now available.</p><p>Please see the <a href=\"http://easyhg.org/\">EasyMercurial website</a> for more information.</p>").arg(EASYHG_VERSION).arg(version));
+        QMessageBox::information(this, title, text);
+        settings.setValue(tag, false);
+    }
+    settings.endGroup();
+}
+
+void MainWindow::help()
+{
+    if (!m_helpDialog) {
+        m_helpDialog = new QDialog;
+        QGridLayout *layout = new QGridLayout;
+        m_helpDialog->setLayout(layout);
+        QPushButton *home = new QPushButton;
+        home->setIcon(QIcon(":images/home.png"));
+        layout->addWidget(home, 0, 0);
+        QPushButton *back = new QPushButton;
+        back->setIcon(QIcon(":images/back.png"));
+        layout->addWidget(back, 0, 1);
+        QPushButton *fwd = new QPushButton;
+        fwd->setIcon(QIcon(":images/forward.png"));
+        layout->addWidget(fwd, 0, 2);
+        QTextBrowser *text = new QTextBrowser;
+        text->setOpenExternalLinks(true);
+        layout->addWidget(text, 1, 0, 1, 4);
+        text->setSource(QUrl("qrc:help/topics.html"));
+        QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close);
+        connect(bb, SIGNAL(rejected()), m_helpDialog, SLOT(hide()));
+        connect(text, SIGNAL(backwardAvailable(bool)),
+                back, SLOT(setEnabled(bool)));
+        connect(text, SIGNAL(forwardAvailable(bool)),
+                fwd, SLOT(setEnabled(bool)));
+        connect(home, SIGNAL(clicked()), text, SLOT(home()));
+        connect(back, SIGNAL(clicked()), text, SLOT(backward()));
+        connect(fwd, SIGNAL(clicked()), text, SLOT(forward()));
+        back->setEnabled(false);
+        fwd->setEnabled(false);
+        layout->addWidget(bb, 2, 0, 1, 4);
+        layout->setColumnStretch(3, 20);
+        m_helpDialog->resize(450, 500);
+    }
+    QTextBrowser *tb = m_helpDialog->findChild<QTextBrowser *>();
+    if (tb) tb->home();
+    m_helpDialog->show();
+    m_helpDialog->raise();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mainwindow.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,268 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include "hgtabwidget.h"
+#include "hgrunner.h"
+#include "common.h"
+#include "changeset.h"
+#include "hgaction.h"
+#include "settingsdialog.h"
+
+#include <QMainWindow>
+#include <QListWidget>
+
+QT_BEGIN_NAMESPACE
+class QAction;
+class QMenu;
+class QTimer;
+QT_END_NAMESPACE
+
+class WorkStatusWidget;
+class FsWatcher;
+
+class MainWindow : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    MainWindow(QString myDirPath);
+
+protected:
+    void closeEvent(QCloseEvent *event);
+    void resizeEvent(QResizeEvent *event);
+
+public slots:
+    void open(QString local);
+    void hgRefresh();
+    void commandStarting(HgAction);
+    void commandCompleted(HgAction action, QString stdOut);
+    void commandFailed(HgAction action, QString stdErr, QString stdOut);
+    void commandCancelled(HgAction action);
+    void enableDisableActions();
+
+private slots:
+    void about();
+    void help();
+    void settings();
+    void settings(SettingsDialog::Tab);
+    void open();
+    void recentMenuActivated();
+    void changeRemoteRepo();
+    void changeRemoteRepo(bool initial);
+    void startupDialog();
+    void clearSelections();
+    void showAllChanged();
+
+    void hgTest();
+    void hgTestExtension();
+    void hgQueryPaths();
+    void hgStat();
+    void hgRemove();
+    void hgAdd();
+    void hgCommit();
+    void hgShowSummary();
+    void hgShowSummaryFor(Changeset *);
+    void hgFolderDiff();
+    void hgDiffToCurrent(QString);
+    void hgDiffToParent(QString, QString);
+    void hgUpdate();
+    void hgRevert();
+    void hgMerge();
+    void hgRedoMerge();
+    void hgCloneFromRemote();
+    void hgInit();
+    void hgIncoming();
+    void hgPush();
+    void hgPull();
+    void hgUpdateToRev(QString);
+    void hgMergeFrom(QString);
+    void hgResolveList();
+    void hgTag(QString);
+    void hgNewBranch();
+    void hgNoBranch();
+    void hgCloseBranch();
+    void hgServe();
+    void hgIgnore();
+    void hgEditIgnore();
+
+    void hgAnnotateFiles(QStringList);
+    void hgDiffFiles(QStringList);
+    void hgCommitFiles(QStringList);
+    void hgRevertFiles(QStringList);
+    void hgRenameFiles(QStringList);
+    void hgCopyFiles(QStringList);
+    void hgAddFiles(QStringList);
+    void hgRemoveFiles(QStringList);
+    void hgRedoFileMerges(QStringList);
+    void hgMarkFilesResolved(QStringList);
+    void hgIgnoreFiles(QStringList);
+    void hgUnIgnoreFiles(QStringList);
+    void hgShowIn(QStringList);
+
+    void updateFsWatcher();
+    void checkFilesystem();
+
+    void newerVersionAvailable(QString);
+
+private:
+    void hgQueryBranch();
+    void hgQueryHeadsActive();
+    void hgQueryHeads();
+    void hgQueryParents();
+    void hgLog();
+    void hgLogIncremental(QStringList prune);
+
+    void initHgIgnore();
+
+    void updateRecentMenu();
+    void createActions();
+    void connectActions();
+    void connectTabsSignals();
+    void createMenus();
+    void createToolBars();
+    void updateToolBarStyle();
+    void createStatusBar();
+    void readSettings();
+    void splitChangeSets(QStringList *list, QString hgLogOutput);
+    void reportNewRemoteHeads(QString);
+    void reportAuthFailed(QString);
+    void writeSettings();
+
+    QStringList listAllUpIpV4Addresses();
+    QString filterTag(QString tag);
+
+    QString getUserInfo() const;
+
+    bool openLocal(QString);
+    bool openRemote(QString, QString);
+    bool openInit(QString);
+
+    bool complainAboutFilePath(QString);
+    bool complainAboutUnknownFolder(QString);
+    bool complainAboutInitInRepo(QString);
+    bool complainAboutInitFile(QString);
+    bool complainAboutCloneToExisting(QString);
+    bool complainAboutCloneToFile(QString);
+    QString complainAboutCloneToExistingFolder(QString local, QString remote); // returns new location, or empty string for cancel
+
+    bool askAboutUnknownFolder(QString);
+    bool askToInitExisting(QString);
+    bool askToInitNew(QString);
+    bool askToOpenParentRepo(QString, QString);
+    bool askToOpenInsteadOfInit(QString);
+
+    void showIncoming(QString);
+    void showPullResult(QString);
+    void showPushResult(QString);
+    int extractChangeCount(QString);
+    QString format1(QString);
+    QString format3(QString, QString, QString);
+
+    void clearState();
+
+    void updateClosedHeads();
+
+    void updateWorkFolderAndRepoNames();
+
+    WorkStatusWidget *m_workStatus;
+    HgTabWidget *m_hgTabs;
+
+    QString m_remoteRepoPath;
+    QString m_workFolderPath;
+    QString m_currentBranch;
+    Changesets m_currentHeads;
+    Changesets m_activeHeads;
+    QSet<QString> m_closedHeadIds;
+    Changesets m_currentParents;
+    int m_commitsSincePush;
+    bool m_stateUnknown;
+    bool m_hgIsOK;
+    bool m_needNewLog;
+
+    bool m_firstStart;
+
+    bool m_showAllFiles;
+
+    //Actions enabled flags
+    bool m_remoteRepoActionsEnabled;
+    bool m_localRepoActionsEnabled;
+
+    QString m_myDirPath;
+
+    // File menu actions
+    QAction *m_openAct;
+    QAction *m_changeRemoteRepoAct;
+    QAction *m_settingsAct;
+    QAction *m_exitAct;
+
+    // Repo actions
+    QAction *m_hgIncomingAct;
+    QAction *m_hgPushAct;
+    QAction *m_hgPullAct;
+    QAction *m_hgRefreshAct;
+    QAction *m_hgFolderDiffAct;
+    QAction *m_hgChgSetDiffAct;
+    QAction *m_hgRevertAct;
+    QAction *m_hgAddAct;
+    QAction *m_hgRemoveAct;
+    QAction *m_hgIgnoreAct;
+    QAction *m_hgUpdateAct;
+    QAction *m_hgCommitAct;
+    QAction *m_hgMergeAct;
+    QAction *m_hgUpdateToRevAct;
+    QAction *m_hgAnnotateAct;
+    QAction *m_hgEditIgnoreAct;
+    QAction *m_hgServeAct;
+
+    // Menus
+    QMenu *m_fileMenu;
+    QMenu *m_recentMenu;
+    QMenu *m_helpMenu;
+
+    // Help menu actions
+    QAction *m_aboutAct;
+    QAction *m_helpAct;
+
+    QToolBar *m_repoToolBar;
+    QToolBar *m_workFolderToolBar;
+
+    QDialog *m_helpDialog;
+
+    HgRunner *m_runner;
+
+    bool m_shouldHgStat;
+
+    QString getDiffBinaryName();
+    QString getMergeBinaryName();
+    QString getEditorBinaryName();
+
+    FsWatcher *m_fsWatcher;
+    int m_fsWatcherToken;
+    bool m_commandSequenceInProgress;
+
+    QString m_lastStatOutput;
+    QStringList m_lastRevertedFiles;
+
+    bool m_justMerged;
+    QString m_mergeTargetRevision;
+    QString m_mergeCommitComment;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/moreinformationdialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,143 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 "moreinformationdialog.h"
+
+#include <QMessageBox>
+#include <QLabel>
+#include <QGridLayout>
+#include <QTextEdit>
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include <QApplication>
+#include <QStyle>
+
+MoreInformationDialog::MoreInformationDialog(QString title,
+                                             QString head,
+                                             QString text,
+                                             QString more,
+                                             QWidget *parent) :
+    QDialog(parent)
+{
+    setWindowTitle(title);
+
+    m_layout = new QGridLayout;
+    m_layout->setSpacing(10);
+    setLayout(m_layout);
+
+    m_iconLabel = new QLabel;
+    m_layout->addWidget(m_iconLabel, 0, 0, 2, 1, Qt::AlignTop);
+
+    QLabel *headLabel = new QLabel(QString("<qt><h3>%1</h3></qt>").arg(head));
+    m_layout->addWidget(headLabel, 0, 1);
+
+    QLabel *textLabel = new QLabel(text);
+    textLabel->setTextFormat(Qt::RichText);
+    textLabel->setWordWrap(true);
+    m_layout->addWidget(textLabel, 1, 1, Qt::AlignTop);
+
+    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
+    connect(bb, SIGNAL(accepted()), this, SLOT(accept()));
+    m_layout->addWidget(bb, 2, 0, 1, 2);
+
+    m_moreButton = bb->addButton(tr("More Details..."),
+                                 QDialogButtonBox::ActionRole);
+    m_moreButton->setAutoDefault(false);
+    m_moreButton->setDefault(false);
+
+    connect(m_moreButton, SIGNAL(clicked()), this, SLOT(moreClicked()));
+
+    bb->button(QDialogButtonBox::Ok)->setDefault(true);
+
+    m_moreText = new QTextEdit();
+    m_moreText->setAcceptRichText(false);
+    m_moreText->document()->setPlainText(more);
+    m_moreText->setMinimumWidth(360);
+    m_moreText->setReadOnly(true);
+    m_moreText->setLineWrapMode(QTextEdit::NoWrap);
+
+    QFont font("Monospace");
+    font.setStyleHint(QFont::TypeWriter);
+    m_moreText->setFont(font);
+
+    m_layout->addWidget(m_moreText, 3, 0, 1, 2);
+
+    m_moreText->hide();
+    if (more == "") m_moreButton->hide();
+
+    m_layout->setRowStretch(1, 10);
+    m_layout->setColumnStretch(1, 20);
+    setMinimumWidth(400);
+}
+
+MoreInformationDialog::~MoreInformationDialog()
+{
+}
+
+void
+MoreInformationDialog::moreClicked()
+{
+    if (m_moreText->isVisible()) {
+        m_moreText->hide();
+        m_layout->setRowStretch(3, 0);
+        m_moreButton->setText(tr("Show Details..."));
+    } else {
+        m_moreText->show();
+        m_layout->setRowStretch(3, 30);
+        m_moreButton->setText(tr("Hide Details..."));
+    }
+    adjustSize();
+}        
+
+void
+MoreInformationDialog::setIcon(QIcon icon)
+{
+    QStyle *style = qApp->style();
+    int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this);
+    m_iconLabel->setPixmap(icon.pixmap(iconSize, iconSize));
+}
+
+void
+MoreInformationDialog::critical(QWidget *parent, QString title, QString head,
+				QString text, QString more)
+{
+    MoreInformationDialog d(title, head, text, more, parent);
+    QStyle *style = qApp->style();
+    d.setIcon(style->standardIcon(QStyle::SP_MessageBoxCritical, 0, &d));
+    d.exec();
+}
+
+void
+MoreInformationDialog::information(QWidget *parent, QString title, QString head,
+                                   QString text, QString more)
+{
+    MoreInformationDialog d(title, head, text, more, parent);
+    QStyle *style = qApp->style();
+    d.setIcon(style->standardIcon(QStyle::SP_MessageBoxInformation, 0, &d));
+    d.exec();
+}
+
+void
+MoreInformationDialog::warning(QWidget *parent, QString title, QString head,
+                               QString text, QString more)
+{
+    MoreInformationDialog d(title, head, text, more, parent);
+    QStyle *style = qApp->style();
+    d.setIcon(style->standardIcon(QStyle::SP_MessageBoxWarning, 0, &d));
+    d.exec();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/moreinformationdialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,65 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 MORE_INFORMATION_DIALOG_H
+#define MORE_INFORMATION_DIALOG_H
+
+#include <QString>
+#include <QDialog>
+
+class QLabel;
+class QTextEdit;
+class QPushButton;
+class QGridLayout;
+
+/**
+ * Provide methods like the QMessageBox static methods, to call up
+ * dialogs with "More information" buttons in them.  QMessageBox does
+ * have an optional additional-details field, but it doesn't behave
+ * quite as we'd like with regard to layout
+ */
+
+class MoreInformationDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    MoreInformationDialog(QString title,
+                          QString head,
+                          QString text,
+                          QString more,
+                          QWidget *parent = 0);
+
+    ~MoreInformationDialog();
+
+    void setIcon(QIcon);
+
+    static void critical(QWidget *parent, QString title, QString head, QString text, QString more);
+    static void information(QWidget *parent, QString title, QString head, QString text, QString more);
+    static void warning(QWidget *parent, QString title, QString head, QString text, QString more);
+
+private slots:
+    void moreClicked();
+
+private:
+    QGridLayout *m_layout;
+    QLabel *m_iconLabel;
+    QPushButton *m_moreButton;
+    QTextEdit *m_moreText;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/multichoicedialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,405 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 "multichoicedialog.h"
+
+#include "selectablelabel.h"
+
+#include "debug.h"
+
+#include <QDialogButtonBox>
+#include <QToolButton>
+#include <QPushButton>
+#include <QFont>
+#include <QDir>
+#include <QFileDialog>
+#include <QDesktopServices>
+#include <QUrl>
+
+MultiChoiceDialog::MultiChoiceDialog(QString title, QString heading,
+                                     QString helpUrl, QWidget *parent) :
+    QDialog(parent),
+    m_helpUrl(helpUrl)
+{
+    setModal(true);
+    setWindowTitle(title);
+
+    QGridLayout *outer = new QGridLayout;
+    setLayout(outer);
+
+    outer->addWidget(new QLabel(heading), 0, 0, 1, 3);
+
+    QWidget *innerWidget = new QWidget;
+    outer->addWidget(innerWidget, 1, 0, 1, 3);
+    m_choiceLayout = new QHBoxLayout;
+    innerWidget->setLayout(m_choiceLayout);
+
+    m_descriptionLabel = new QLabel;
+    outer->addWidget(m_descriptionLabel, 2, 0, 1, 3);
+
+    QFont f = m_descriptionLabel->font();
+    f.setPointSize(f.pointSize() * 0.95);
+    m_descriptionLabel->setFont(f);
+
+    m_urlLabel = new QLabel(tr("&URL:"));
+    outer->addWidget(m_urlLabel, 3, 0);
+
+    m_urlCombo = new QComboBox();
+    m_urlCombo->setEditable(true);
+    m_urlLabel->setBuddy(m_urlCombo);
+    connect(m_urlCombo, SIGNAL(editTextChanged(const QString &)),
+            this, SLOT(urlChanged(const QString &)));
+    outer->addWidget(m_urlCombo, 3, 1, 1, 2);
+
+    m_fileLabel = new QLabel(tr("&File:"));
+    outer->addWidget(m_fileLabel, 4, 0);
+
+    m_fileCombo = new QComboBox();
+    m_fileCombo->setEditable(true);
+    m_fileLabel->setBuddy(m_fileCombo);
+    connect(m_fileCombo, SIGNAL(editTextChanged(const QString &)),
+            this, SLOT(fileChanged(const QString &)));
+    outer->addWidget(m_fileCombo, 4, 1);
+    outer->setColumnStretch(1, 20);
+
+    m_browseButton = new QPushButton(tr("Browse..."));
+    outer->addWidget(m_browseButton, 4, 2);
+    connect(m_browseButton, SIGNAL(clicked()), this, SLOT(browse()));
+
+    outer->addItem(new QSpacerItem(2, 12), 5, 0);
+
+    QDialogButtonBox *bbox;
+    if (helpUrl != "") {
+        bbox = new QDialogButtonBox(QDialogButtonBox::Help |
+                                    QDialogButtonBox::Ok |
+                                    QDialogButtonBox::Cancel);
+    } else {
+        bbox = new QDialogButtonBox(QDialogButtonBox::Ok |
+                                    QDialogButtonBox::Cancel);
+    }        
+    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
+    connect(bbox, SIGNAL(rejected()), this, SLOT(reject()));
+    connect(bbox, SIGNAL(helpRequested()), this, SLOT(helpRequested()));
+    outer->addWidget(bbox, 6, 0, 1, 3);
+
+    m_okButton = bbox->button(QDialogButtonBox::Ok);
+    updateOkButton();
+
+    setMinimumWidth(480);
+}
+
+void
+MultiChoiceDialog::helpRequested()
+{
+    QDesktopServices::openUrl(m_helpUrl);
+}
+
+QString
+MultiChoiceDialog::getCurrentChoice()
+{
+    return m_currentChoice;
+}
+
+void
+MultiChoiceDialog::setCurrentChoice(QString c)
+{
+    m_currentChoice = c;
+    choiceChanged();
+}
+
+QString
+MultiChoiceDialog::getArgument()
+{
+    if (m_argTypes[m_currentChoice] == UrlArg ||
+        m_argTypes[m_currentChoice] == UrlToDirectoryArg) {
+        return m_urlCombo->currentText();
+    } else {
+        return m_fileCombo->currentText();
+    }
+}
+
+QString
+MultiChoiceDialog::getAdditionalArgument()
+{
+    if (m_argTypes[m_currentChoice] == UrlToDirectoryArg) {
+        return m_fileCombo->currentText();
+    } else {
+        return "";
+    }
+}
+
+void
+MultiChoiceDialog::addRecentArgument(QString id, QString arg,
+                                     bool additionalArgument)
+{
+    if (additionalArgument) {
+        RecentFiles(QString("Recent-%1-add").arg(id)).addFile(arg);
+    } else {
+        RecentFiles(QString("Recent-%1").arg(id)).addFile(arg);
+    }
+}
+
+void
+MultiChoiceDialog::addChoice(QString id, QString text,
+                             QString description, ArgType arg,
+                             bool defaultEmpty)
+{
+    bool first = (m_texts.empty());
+
+    m_texts[id] = text;
+    m_descriptions[id] = description;
+    m_argTypes[id] = arg;
+    m_defaultEmpty[id] = defaultEmpty;
+    
+    if (arg != NoArg) {
+        m_recentFiles[id] = QSharedPointer<RecentFiles>
+            (new RecentFiles(QString("Recent-%1").arg(id)));
+    }
+
+    SelectableLabel *cb = new SelectableLabel;
+    cb->setSelectedText(text);
+    cb->setUnselectedText(text);
+    cb->setMaximumWidth(270);
+
+    m_choiceLayout->addWidget(cb);
+    m_choiceButtons[cb] = id;
+
+    connect(cb, SIGNAL(selectionChanged()), this, SLOT(choiceChanged()));
+
+    if (first) {
+        m_currentChoice = id;
+        choiceChanged();
+    }
+}
+
+QString
+MultiChoiceDialog::getDefaultPath() const
+{
+    QDir home(QDir::home());
+    QDir dflt;
+
+    dflt = QDir(home.filePath(tr("Documents")));
+    DEBUG << "testing " << dflt << endl;
+    if (dflt.exists()) return dflt.canonicalPath();
+
+    dflt = QDir(home.filePath(tr("My Documents")));
+    DEBUG << "testing " << dflt << endl;
+    if (dflt.exists()) return dflt.canonicalPath();
+
+    dflt = QDir(home.filePath(tr("Desktop")));
+    DEBUG << "testing " << dflt << endl;
+    if (dflt.exists()) return dflt.canonicalPath();
+
+    DEBUG << "all failed, returning " << home << endl;
+    return home.canonicalPath();
+}
+
+void
+MultiChoiceDialog::browse()
+{
+    QString origin = getArgument();
+
+    if (origin == "") {
+        origin = getDefaultPath();
+    }
+
+    QString path = origin;
+
+    if (m_argTypes[m_currentChoice] == DirectoryArg ||
+        m_argTypes[m_currentChoice] == UrlToDirectoryArg) {
+
+        path = QFileDialog::getExistingDirectory
+            (this, tr("Open Directory"), origin,
+             QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
+        if (path != QString()) {
+            m_fileCombo->lineEdit()->setText(path + QDir::separator());
+        }
+
+    } else {
+
+        path = QFileDialog::getOpenFileName
+            (this, tr("Open File"), origin);
+        if (path != QString()) {
+            m_fileCombo->lineEdit()->setText(path);
+        }
+    }
+}
+
+void
+MultiChoiceDialog::urlChanged(const QString &)
+{
+    updateOkButton();
+    updateFileComboFromURL();
+}
+
+void
+MultiChoiceDialog::fileChanged(const QString &)
+{
+    updateOkButton();
+}
+
+void
+MultiChoiceDialog::updateFileComboFromURL()
+{
+    if (m_argTypes[m_currentChoice] != UrlToDirectoryArg) {
+        return;
+    }
+    QString url = m_urlCombo->currentText();
+    if (QRegExp("^\\w+://").indexIn(url) < 0) {
+        return;
+    }
+    QString urlDirName = url;
+    urlDirName.replace(QRegExp("^.*\\//.*\\/"), "");
+    if (urlDirName == "" || urlDirName == url) {
+        return;
+    }
+    QString dirPath = m_fileCombo->currentText();
+    QString defaultPath = getDefaultPath();
+    if (dirPath == defaultPath) {
+        dirPath += QDir::separator() + urlDirName;
+    } else if (dirPath == defaultPath + QDir::separator()) {
+        dirPath += urlDirName;
+    } else {
+        QDir d(dirPath);
+        d.cdUp();
+        dirPath = d.filePath(urlDirName);
+    }
+    m_fileCombo->lineEdit()->setText(dirPath);
+}
+
+void
+MultiChoiceDialog::updateOkButton()
+{
+    if (m_defaultEmpty[m_currentChoice]) {
+        m_okButton->setEnabled(true);
+    } else if (m_argTypes[m_currentChoice] == UrlToDirectoryArg) {
+        m_okButton->setEnabled(getArgument() != "" &&
+                               getAdditionalArgument() != "");
+    } else {
+        m_okButton->setEnabled(getArgument() != "");
+    }
+}
+
+bool
+MultiChoiceDialog::urlComboNotUrl() const
+{
+    QString url = m_urlCombo->currentText();
+    if (QRegExp("^\\w+://").indexIn(url) < 0) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void
+MultiChoiceDialog::choiceChanged()
+{
+    DEBUG << "choiceChanged" << endl;
+
+    if (m_choiceButtons.empty()) return;
+
+    QString id = "";
+
+    QObject *s = sender();
+    QWidget *w = qobject_cast<QWidget *>(s);
+    if (w) id = m_choiceButtons[w];
+
+    if (id == m_currentChoice) return;
+    if (id == "") {
+        // Happens when this is called for the very first time, when
+        // m_currentChoice has been set to the intended ID but no
+        // button has actually been pressed -- then we need to
+        // initialise
+        id = m_currentChoice;
+    }
+
+    m_currentChoice = id;
+
+    foreach (QWidget *cw, m_choiceButtons.keys()) {
+        SelectableLabel *sl = qobject_cast<SelectableLabel *>(cw);
+        if (sl) {
+            sl->setSelected(m_choiceButtons[cw] == id);
+        }
+    }
+
+    m_descriptionLabel->setText(m_descriptions[id]);
+
+    m_browseButton->hide();
+
+    m_urlLabel->hide();
+    m_urlCombo->clear();
+    m_urlCombo->hide();
+
+    m_fileLabel->hide();
+    m_fileCombo->clear();
+    m_fileCombo->hide();
+
+    QSharedPointer<RecentFiles> rf = m_recentFiles[id];
+
+    switch (m_argTypes[id]) {
+        
+    case NoArg:
+        break;
+
+    case FileArg:
+        m_fileLabel->setText(tr("&File:"));
+        m_fileLabel->show();
+        m_fileCombo->show();
+        m_fileCombo->addItems(rf->getRecent());
+        if (m_defaultEmpty[id]) m_fileCombo->lineEdit()->setText("");
+        m_browseButton->show();
+        break;
+
+    case DirectoryArg:
+        m_fileLabel->setText(tr("&Folder:"));
+        m_fileLabel->show();
+        m_fileCombo->show();
+        m_fileCombo->addItems(rf->getRecent());
+        if (m_defaultEmpty[id]) m_fileCombo->lineEdit()->setText("");
+        m_browseButton->show();
+        break;
+
+    case UrlArg:
+        m_urlLabel->show();
+        m_urlCombo->show();
+        m_urlCombo->addItems(rf->getRecent());
+        if (m_defaultEmpty[id] || urlComboNotUrl()) {
+            m_urlCombo->lineEdit()->setText("");
+        }
+        break;
+
+    case UrlToDirectoryArg:
+        m_urlLabel->show();
+        m_urlCombo->show();
+        m_urlCombo->addItems(rf->getRecent());
+        if (m_defaultEmpty[id] || urlComboNotUrl()) {
+            m_urlCombo->lineEdit()->setText("");
+        }
+        m_fileLabel->setText(tr("&Folder:"));
+        m_fileLabel->show();
+        m_fileCombo->show();
+        m_fileCombo->lineEdit()->setText(getDefaultPath());
+        updateFileComboFromURL();
+        m_browseButton->show();
+        break;
+    }
+
+    updateOkButton();
+    adjustSize();
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/multichoicedialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,99 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 MULTICHOICEDIALOG_H
+#define MULTICHOICEDIALOG_H
+
+#include "recentfiles.h"
+
+#include <QDialog>
+#include <QString>
+#include <QAbstractButton>
+#include <QMap>
+#include <QLabel>
+#include <QLineEdit>
+#include <QGridLayout>
+#include <QHBoxLayout>
+#include <QStackedWidget>
+#include <QSharedPointer>
+#include <QComboBox>
+
+class MultiChoiceDialog : public QDialog
+{
+    Q_OBJECT
+public:
+    explicit MultiChoiceDialog(QString title,
+                               QString heading,
+                               QString helpUrl = "",
+                               QWidget *parent = 0);
+
+    enum ArgType {
+        NoArg,
+        FileArg,
+        DirectoryArg,
+        UrlArg,
+        UrlToDirectoryArg
+    };
+
+    void addChoice(QString identifier, QString text,
+                   QString description, ArgType arg,
+                   bool defaultEmpty = false);
+
+    void setCurrentChoice(QString);
+    QString getCurrentChoice();
+    QString getArgument();
+    QString getAdditionalArgument();
+
+    static void addRecentArgument(QString identifier, QString name,
+                                  bool additionalArgument = false);
+
+private slots:
+    void choiceChanged();
+    void urlChanged(const QString &);
+    void fileChanged(const QString &);
+    void helpRequested();
+    void browse();
+
+private:
+    void updateFileComboFromURL();
+    void updateOkButton();
+    
+    QString m_helpUrl;
+
+    QMap<QString, QString> m_texts;
+    QMap<QString, QString> m_descriptions;
+    QMap<QString, ArgType> m_argTypes;
+    QMap<QString, bool> m_defaultEmpty;
+    QMap<QString, QSharedPointer<RecentFiles> > m_recentFiles;
+
+    QString m_currentChoice;
+    QMap<QWidget *, QString> m_choiceButtons;
+
+    QHBoxLayout *m_choiceLayout;
+    QLabel *m_descriptionLabel;
+    QLabel *m_fileLabel;
+    QComboBox *m_fileCombo;
+    QAbstractButton *m_browseButton;
+    QLabel *m_urlLabel;
+    QComboBox *m_urlCombo;
+    QAbstractButton *m_okButton;
+
+    QString getDefaultPath() const;
+    bool urlComboNotUrl() const;
+};
+
+#endif // MULTICHOICEDIALOG_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/panned.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,306 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "debug.h"
+
+#include <QScrollBar>
+#include <QWheelEvent>
+#include <QTimer>
+
+#include <cmath>
+
+#include <iostream>
+
+Panned::Panned() :
+    m_dragging(false)
+{
+    m_dragTimer = new QTimer(this);
+    m_dragTimerMs = 50;
+    connect(m_dragTimer, SIGNAL(timeout()), this, SLOT(dragTimerTimeout()));
+    setRenderHints(QPainter::Antialiasing |
+                   QPainter::TextAntialiasing);
+}
+
+void
+Panned::resizeEvent(QResizeEvent *ev)
+{
+    DEBUG << "Panned::resizeEvent()" << endl;
+
+    QPointF nearpt = mapToScene(0, 0);
+    QPointF farpt = mapToScene(width(), height());
+    QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y());
+    QRectF pr(nearpt, sz);
+
+    QGraphicsView::resizeEvent(ev);
+
+    if (pr != m_pannedRect) {
+        DEBUG << "Panned: setting panned rect to " << pr << endl;
+        m_pannedRect = pr;
+        centerOn(pr.center());
+        emit pannedRectChanged(pr);
+    }
+}
+
+void
+Panned::setScene(QGraphicsScene *s)
+{
+    if (!scene()) {
+        QGraphicsView::setScene(s);
+        return;
+    }
+
+    QPointF nearpt = mapToScene(0, 0);
+    QPointF farpt = mapToScene(width(), height());
+    QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y());
+    QRectF pr(nearpt, sz);
+
+    QGraphicsView::setScene(s);
+
+    DEBUG << "Panned::setScene: pr = " << pr << ", sceneRect = " << sceneRect() << endl;
+
+    if (scene() && sceneRect().intersects(pr)) {
+        DEBUG << "Panned::setScene: restoring old rect " << pr << endl;
+        m_pannedRect = pr;
+        centerOn(pr.center());
+        emit pannedRectChanged(pr);
+    }
+}
+
+void
+Panned::paintEvent(QPaintEvent *e)
+{
+    QGraphicsView::paintEvent(e);
+}
+
+void
+Panned::drawForeground(QPainter *, const QRectF &)
+{
+    QPointF nearpt = mapToScene(0, 0);
+    QPointF farpt = mapToScene(width(), height());
+    QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y());
+    QRectF pr(nearpt, sz);
+
+    if (pr != m_pannedRect) {
+        DEBUG << "Panned::drawForeground: visible rect " << pr << " differs from panned rect " << m_pannedRect << ", updating panned rect" <<endl;
+        if (pr.x() != m_pannedRect.x()) emit pannedContentsScrolled();
+        m_pannedRect = pr;
+        emit pannedRectChanged(pr);
+    }
+}
+
+void
+Panned::zoomIn()
+{
+    QMatrix m = matrix();
+    m.scale(1.0 / 1.1, 1.0 / 1.1);
+    setMatrix(m);
+}
+
+void
+Panned::zoomOut()
+{
+    QMatrix m = matrix();
+    m.scale(1.1, 1.1);
+    setMatrix(m);
+}
+
+void
+Panned::slotSetPannedRect(QRectF pr)
+{
+    centerOn(pr.center());
+//	setSceneRect(pr);
+//	m_pannedRect = pr;
+}
+
+void
+Panned::wheelEvent(QWheelEvent *ev)
+{
+    if (ev->modifiers() & Qt::ControlModifier) {
+        int d = ev->delta();
+        if (d > 0) {
+            while (d > 0) {
+                zoomOut();
+                d -= 120;
+            }
+        } else {
+            while (d < 0) {
+                zoomIn();
+                d += 120;
+            }
+        }
+    } else {
+        emit wheelEventReceived(ev);
+        QGraphicsView::wheelEvent(ev);
+    }
+}
+
+void
+Panned::slotEmulateWheelEvent(QWheelEvent *ev)
+{
+    QGraphicsView::wheelEvent(ev);
+}
+
+void
+Panned::mousePressEvent(QMouseEvent *ev)
+{
+    if (dragMode() != QGraphicsView::ScrollHandDrag ||
+        ev->button() != Qt::LeftButton) {
+        QGraphicsView::mousePressEvent(ev);
+        return;
+    }
+
+    DEBUG << "Panned::mousePressEvent: have left button in drag mode" << endl;
+
+    setDragMode(QGraphicsView::NoDrag);
+    QGraphicsView::mousePressEvent(ev);
+    setDragMode(QGraphicsView::ScrollHandDrag);
+
+    if (!ev->isAccepted()) {
+        ev->accept();
+        m_dragging = true;
+        m_lastDragPos = ev->pos();
+        m_lastDragStart = ev->pos();
+        m_lastOrigin = QPoint(horizontalScrollBar()->value(),
+                              verticalScrollBar()->value());
+        m_velocity = QPointF(0, 0);
+        m_dragTimer->start(m_dragTimerMs);
+        m_dragDirection = UnknownDrag;
+    }
+}
+
+void
+Panned::updateDragDirection(QPoint pos)
+{
+    if (m_dragDirection == FreeDrag) {
+        return;
+    }
+
+    QPoint overall = pos - m_lastDragStart;
+
+    int smallThreshold = 10;
+    int largeThreshold = 30;
+    int dx = qAbs(overall.x());
+    int dy = qAbs(overall.y());
+
+    switch (m_dragDirection) {
+
+    case UnknownDrag:
+        if (dx > smallThreshold) {
+            if (dy > smallThreshold) {
+                m_dragDirection = FreeDrag;
+            } else {
+                m_dragDirection = HorizontalDrag;
+            }
+        } else if (dy > smallThreshold) {
+            m_dragDirection = VerticalDrag;
+        }
+        break;
+
+    case HorizontalDrag:
+        if (dy > largeThreshold) {
+            m_dragDirection = FreeDrag;
+        }
+        break;
+
+    case VerticalDrag:
+        if (dx > largeThreshold) {
+            m_dragDirection = FreeDrag;
+        }
+        break;
+
+    case FreeDrag:
+        // stick with it
+        break;
+    };
+}
+
+void
+Panned::mouseMoveEvent(QMouseEvent *ev)
+{
+    if (!m_dragging) {
+        QGraphicsView::mouseMoveEvent(ev);
+        return;
+    }
+    DEBUG << "Panned::mouseMoveEvent: dragging" << endl;
+    ev->accept();
+    updateDragDirection(ev->pos());
+    QScrollBar *hBar = horizontalScrollBar();
+    QScrollBar *vBar = verticalScrollBar();
+    QPoint delta = ev->pos() - m_lastDragPos;
+    if (m_dragDirection != VerticalDrag) {
+        hBar->setValue(hBar->value() +
+                       (isRightToLeft() ? delta.x() : -delta.x()));
+    }
+    if (m_dragDirection != HorizontalDrag) {
+        vBar->setValue(vBar->value() - delta.y());
+    }
+    m_lastDragPos = ev->pos();
+}
+
+void
+Panned::mouseReleaseEvent(QMouseEvent *ev)
+{
+    if (!m_dragging) {
+        QGraphicsView::mouseReleaseEvent(ev);
+        return;
+    }
+    DEBUG << "Panned::mouseReleaseEvent: dragging" << endl;
+    ev->accept();
+    m_dragging = false;
+}
+
+void
+Panned::dragTimerTimeout()
+{
+    QPoint origin = QPoint(horizontalScrollBar()->value(),
+                           verticalScrollBar()->value());
+    if (m_dragging) {
+        m_velocity = QPointF
+            (float(origin.x() - m_lastOrigin.x()) / m_dragTimerMs,
+             float(origin.y() - m_lastOrigin.y()) / m_dragTimerMs);
+        m_lastOrigin = origin;
+        DEBUG << "Panned::dragTimerTimeout: velocity = " << m_velocity << endl;
+    } else {
+        if (origin == m_lastOrigin) {
+            m_dragTimer->stop();
+        }
+        float x = m_velocity.x(), y = m_velocity.y();
+        if (fabsf(x) > 1.0/m_dragTimerMs) x = x * 0.9f;
+        else x = 0.f;
+        if (fabsf(y) > 1.0/m_dragTimerMs) y = y * 0.9f;
+        else y = 0.f;
+        m_velocity = QPointF(x, y);
+        DEBUG << "Panned::dragTimerTimeout: velocity adjusted to " << m_velocity << endl;
+        m_lastOrigin = origin;
+        //!!! need to store origin in floats
+        if (m_dragDirection != VerticalDrag) {
+            horizontalScrollBar()->setValue(m_lastOrigin.x() +
+                                            m_velocity.x() * m_dragTimerMs);
+        }
+        if (m_dragDirection != HorizontalDrag) {
+            verticalScrollBar()->setValue(m_lastOrigin.y() +
+                                          m_velocity.y() * m_dragTimerMs);
+        }
+    }
+}
+
+void
+Panned::leaveEvent(QEvent *)
+{
+    emit mouseLeaves();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/panned.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,85 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _PANNED_H_
+#define _PANNED_H_
+
+#include <QGraphicsView>
+
+class QWheelEvent;
+class QEvent;
+class QTimer;
+
+class Panned : public QGraphicsView
+{
+    Q_OBJECT
+
+public:
+    Panned();
+    virtual ~Panned() { }
+
+    virtual void setScene(QGraphicsScene *s);
+
+signals:
+    void pannedRectChanged(QRectF);
+    void wheelEventReceived(QWheelEvent *);
+    void pannedContentsScrolled();
+    void mouseLeaves();
+
+public slots:
+    void slotSetPannedRect(QRectF);
+    void slotEmulateWheelEvent(QWheelEvent *ev);
+
+    void zoomIn();
+    void zoomOut();
+
+private slots:
+    void dragTimerTimeout();
+
+protected:
+    QRectF m_pannedRect;
+
+    QPoint m_lastDragPos;
+    QPoint m_lastDragStart;
+    QPoint m_lastOrigin;
+    QPointF m_velocity;
+    bool m_dragging;
+    int m_dragTimerMs;
+    QTimer *m_dragTimer;
+
+    enum DragDirection {
+        UnknownDrag,
+        HorizontalDrag,
+        VerticalDrag,
+        FreeDrag
+    };
+    DragDirection m_dragDirection;
+    void updateDragDirection(QPoint);
+
+    virtual void mousePressEvent(QMouseEvent *);
+    virtual void mouseMoveEvent(QMouseEvent *);
+    virtual void mouseReleaseEvent(QMouseEvent *);
+
+    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/src/panner.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,299 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "debug.h"
+
+#include <QPolygon>
+#include <QMouseEvent>
+#include <QColor>
+
+#include <iostream>
+
+class PannerScene : public QGraphicsScene
+{
+public:
+    friend class Panner;
+};
+
+Panner::Panner() :
+    m_clicked(false),
+    m_moved(false)
+{
+    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    setOptimizationFlags(QGraphicsView::DontSavePainterState |
+                         QGraphicsView::IndirectPainting);
+    setMouseTracking(true);
+    setInteractive(false);
+}
+
+void
+Panner::fit(QRectF r)
+{
+    Qt::AspectRatioMode m = Qt::IgnoreAspectRatio;
+    if (height() > width()) {
+        // Our panner is vertical; if the source is not tall,
+        // don't stretch it to fit in the height, it'd look weird
+        if (r.height() < height() * 2) {
+            m = Qt::KeepAspectRatio;
+        }
+    } else {
+        // Similarly, but horizontal
+        if (r.width() < width() * 2) {
+            m = Qt::KeepAspectRatio;
+        }
+    }
+    DEBUG << "Panner: fit mode " << int(m) << endl;
+    fitInView(r, m);
+}
+
+void
+Panner::setScene(QGraphicsScene *s)
+{
+    if (scene()) {
+        disconnect(scene(), SIGNAL(changed(const QList<QRectF> &)),
+                   this, SLOT(slotSceneChanged(const QList<QRectF> &)));
+        disconnect(scene(), SIGNAL(sceneRectChanged(const QRectF &)),
+                   this, SLOT(slotSceneRectChanged(const QRectF &)));
+    }
+    QGraphicsView::setScene(s);
+    m_cache = QPixmap();
+    if (scene()) {
+        QRectF r = sceneRect();
+        DEBUG << "scene rect: " << r << ", my rect " << rect() << endl;
+        fit(r);
+        connect(scene(), SIGNAL(changed(const QList<QRectF> &)),
+                this, SLOT(slotSceneChanged(const QList<QRectF> &)));
+        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)));
+
+    connect(this, SIGNAL(zoomIn()),
+            p, SLOT(zoomIn()));
+
+    connect(this, SIGNAL(zoomOut()),
+            p, SLOT(zoomOut()));
+}
+
+void
+Panner::slotSetPannedRect(QRectF rect) 
+{
+    m_pannedRect = rect;
+    viewport()->update();
+}
+
+void
+Panner::resizeEvent(QResizeEvent *)
+{
+    DEBUG << "Panner::resizeEvent" << endl;
+    if (scene()) fit(sceneRect());
+    m_cache = QPixmap();
+}
+
+void
+Panner::slotSceneRectChanged(const QRectF &newRect)
+{
+    DEBUG << "Panner::slotSceneRectChanged" << endl;
+    if (!scene()) return; // spurious
+    fit(newRect);
+    m_cache = QPixmap();
+    viewport()->update();
+}
+
+void
+Panner::slotSceneChanged(const QList<QRectF> &)
+{
+    DEBUG << "Panner::slotSceneChanged" << endl;
+    if (!scene()) return; // spurious
+    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)
+{
+    DEBUG << "Panner::updateScene" << endl;
+//    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()) {
+
+        DEBUG << "Panner: cache size " << m_cache.size() << " != viewport size " << viewport()->size() << ": recreating cache" << endl;
+
+        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();
+
+        DEBUG << "done" << endl;
+    }
+
+    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_moved = false;
+    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;
+    if (!m_moved) {
+        if ((m_clickedPoint - e->pos()).manhattanLength() > 2) {
+            m_moved = true;
+        } else {
+            return;
+        }
+    }
+    QRectF nr = m_clickedRect;
+    nr.translate(delta);
+    m_pannedRect = nr;
+    emit pannedRectChanged(m_pannedRect);
+    viewport()->update();
+}
+
+void
+Panner::mouseReleaseEvent(QMouseEvent *e)
+{
+    if (e->button() != Qt::LeftButton) {
+        QGraphicsView::mouseDoubleClickEvent(e);
+        return;
+    }
+
+    if (m_clicked) {
+        if (m_moved) {
+            mouseMoveEvent(e);
+        } else {
+            moveTo(e->pos());
+        }
+    }
+
+    m_clicked = false;
+    viewport()->update();
+}
+
+void
+Panner::wheelEvent(QWheelEvent *e)
+{
+    int d = e->delta();
+    if (d > 0) {
+        while (d > 0) {
+            emit zoomOut();
+            d -= 120;
+        }
+    } else {
+        while (d < 0) {
+            emit zoomIn();
+            d += 120;
+        }
+    }
+}
+
+void
+Panner::moveTo(QPoint p)
+{
+    QPointF sp = mapToScene(p);
+    QRectF nr = m_pannedRect;
+    double dx = sp.x() - nr.center().x();
+    double dy = sp.y() - nr.center().y();
+    nr.translate(dx, dy);
+    slotSetPannedRect(nr);
+    emit pannedRectChanged(m_pannedRect);
+    viewport()->update();
+}
+   
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/panner.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,79 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _PANNER_H_
+#define _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 &);
+    void slotSceneChanged(const QList<QRectF> &);
+
+protected:
+    QRectF m_pannedRect;
+
+    void moveTo(QPoint);
+
+    void fit(QRectF);
+
+    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;
+    bool m_moved;
+    QRectF m_clickedRect;
+    QPoint m_clickedPoint;
+
+    QPixmap m_cache;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/recentfiles.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,145 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "recentfiles.h"
+
+#include <QFileInfo>
+#include <QSettings>
+#include <QRegExp>
+
+RecentFiles::RecentFiles(QString settingsGroup, size_t maxCount,
+                         bool ignoreTemporaries) :
+    m_settingsGroup(settingsGroup),
+    m_maxCount(maxCount),
+    m_ignoreTemporaries(ignoreTemporaries)
+{
+    read();
+}
+
+RecentFiles::~RecentFiles()
+{
+    // nothing
+}
+
+void
+RecentFiles::read()
+{
+    m_names.clear();
+    QSettings settings;
+    settings.beginGroup(m_settingsGroup);
+
+    for (size_t i = 0; i < 100; ++i) {
+        QString key = QString("recent-%1").arg(i);
+        QString name = settings.value(key, "").toString();
+        if (name == "") break;
+        if (i < m_maxCount) m_names.push_back(name);
+        else settings.setValue(key, "");
+    }
+
+    settings.endGroup();
+}
+
+void
+RecentFiles::write()
+{
+    QSettings settings;
+    settings.beginGroup(m_settingsGroup);
+
+    for (size_t i = 0; i < m_maxCount; ++i) {
+        QString key = QString("recent-%1").arg(i);
+        QString name = "";
+        if (i < m_names.size()) name = m_names[i];
+        settings.setValue(key, name);
+    }
+
+    settings.endGroup();
+}
+
+void
+RecentFiles::truncateAndWrite()
+{
+    while (m_names.size() > m_maxCount) {
+        m_names.pop_back();
+    }
+    write();
+}
+
+QStringList
+RecentFiles::getRecent() const
+{
+    QStringList names;
+    for (size_t i = 0; i < m_maxCount; ++i) {
+        if (i < m_names.size()) {
+            names.push_back(m_names[i]);
+        }
+    }
+    return names;
+}
+
+void
+RecentFiles::add(QString name)
+{
+    bool have = false;
+    for (size_t i = 0; i < m_names.size(); ++i) {
+        if (m_names[i] == name) {
+            have = true;
+            break;
+        }
+    }
+    
+    if (!have) {
+        m_names.push_front(name);
+    } else {
+        std::deque<QString> newnames;
+        newnames.push_back(name);
+        for (size_t i = 0; i < m_names.size(); ++i) {
+            if (m_names[i] == name) continue;
+            newnames.push_back(m_names[i]);
+        }
+        m_names = newnames;
+    }
+
+    truncateAndWrite();
+    emit recentChanged();
+}
+
+void
+RecentFiles::addFile(QString name)
+{
+    static QRegExp schemeRE("^[a-zA-Z]{2,5}://");
+    static QRegExp tempRE("[\\/][Tt]e?mp[\\/]");
+    if (name.endsWith("/")) {
+        name = name.left(name.length()-1);
+    }
+    if (name == "") {
+        return;
+    }
+    if (schemeRE.indexIn(name) == 0) {
+        add(name);
+    } else {
+        QString absPath = QFileInfo(name).absoluteFilePath();
+        if (tempRE.indexIn(absPath) != -1) {
+            if (!m_ignoreTemporaries) {
+                add(absPath);
+            }
+        } else {
+            add(absPath);
+        }
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/recentfiles.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,83 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _RECENT_FILES_H_
+#define _RECENT_FILES_H_
+
+#include <QObject>
+#include <QString>
+#include <QStringList>
+#include <deque>
+
+/**
+ * RecentFiles manages a list of the names of recently-used objects,
+ * saving and restoring that list via QSettings.  The names do not
+ * actually have to refer to files.
+ */
+
+class RecentFiles : public QObject
+{
+    Q_OBJECT
+
+public:
+    /**
+     * Construct a RecentFiles object that saves and restores in the
+     * given QSettings group and truncates when the given count of
+     * strings is reached.
+     */
+    RecentFiles(QString settingsGroup = "RecentFiles",
+                size_t maxCount = 10,
+                bool ignoreTemporaries = true);
+    virtual ~RecentFiles();
+
+    QString getSettingsGroup() const { return m_settingsGroup; }
+
+    int getMaxCount() const { return m_maxCount; }
+
+    QStringList getRecent() const;
+
+    /**
+     * Add a name that should be treated as a literal string.
+     */
+    void add(QString name);
+    
+    /**
+     * Add a name that is known to be either a file path or a URL.  If
+     * it looks like a URL, add it literally; otherwise treat it as a
+     * file path and canonicalise it appropriately.  Also takes into
+     * account the preference for whether to include temporary files
+     * in the recent files menu: the file will not be added if the
+     * preference is set and the file appears to be a temporary one.
+     */
+    void addFile(QString name);
+
+signals:
+    void recentChanged();
+
+protected:
+    QString m_settingsGroup;
+    size_t m_maxCount;
+    bool m_ignoreTemporaries;
+
+    std::deque<QString> m_names;
+
+    void read();
+    void write();
+    void truncateAndWrite();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/repositorydialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,29 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 "repositorydialog.h"
+
+RepositoryDialog::RepositoryDialog(QWidget *parent) :
+    QDialog(parent)
+{
+    setModal(true);
+    setWindowTitle(tr("Open Repository"));
+
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/repositorydialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,34 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+
+    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 REPOSITORYDIALOG_H
+#define REPOSITORYDIALOG_H
+
+#include <QDialog>
+
+class RepositoryDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    RepositoryDialog(QWidget *parent = 0);
+
+
+
+};
+
+#endif // REPOSITORYDIALOG_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/selectablelabel.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,141 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "selectablelabel.h"
+
+#include "debug.h"
+
+#include <iostream>
+#include <QApplication>
+
+SelectableLabel::SelectableLabel(QWidget *p) :
+    QLabel(p),
+    m_selected(false)
+{
+    setTextFormat(Qt::RichText);
+    setupStyle();
+    setOpenExternalLinks(true);
+}
+
+SelectableLabel::~SelectableLabel()
+{
+}
+
+void
+SelectableLabel::setUnselectedText(QString text)
+{
+    if (m_unselectedText == text) return;
+    m_unselectedText = text;
+    if (!m_selected) {
+        setText(m_unselectedText);
+        resize(sizeHint());
+    }
+}
+
+void
+SelectableLabel::setSelectedText(QString text)
+{
+    if (m_selectedText == text) return;
+    m_selectedText = text;
+    if (m_selected) {
+        setText(m_selectedText);
+        resize(sizeHint());
+    }
+}
+
+void
+SelectableLabel::setupStyle()
+{
+    QPalette palette = QApplication::palette();
+
+    setTextInteractionFlags(Qt::LinksAccessibleByKeyboard |
+                            Qt::LinksAccessibleByMouse |
+                            Qt::TextSelectableByMouse);
+
+    if (m_selected) {
+        setStyleSheet
+            (QString("QLabel { background: %1; border: 1px solid %2; padding: 7px } ")
+             .arg(palette.light().color().name())
+             .arg(palette.dark().color().name()));
+    } else {
+        setStyleSheet
+            (QString("QLabel { border: 0; padding: 7px } "));
+    }
+}    
+
+void
+SelectableLabel::setSelected(bool s)
+{
+    if (m_selected == s) return;
+    m_selected = s;
+    if (m_selected) {
+        setText(m_selectedText);
+    } else {
+        setText(m_unselectedText);
+    }
+    setupStyle();
+    parentWidget()->resize(parentWidget()->sizeHint());
+}
+
+void
+SelectableLabel::toggle()
+{
+    setSelected(!m_selected);
+}
+
+void
+SelectableLabel::mousePressEvent(QMouseEvent *e)
+{
+    m_swallowRelease = !m_selected;
+    setSelected(true);
+    QLabel::mousePressEvent(e);
+    emit selectionChanged();
+}
+
+void
+SelectableLabel::mouseDoubleClickEvent(QMouseEvent *e)
+{
+    QLabel::mouseDoubleClickEvent(e);
+    emit doubleClicked();
+}
+
+void
+SelectableLabel::mouseReleaseEvent(QMouseEvent *e)
+{
+    if (!m_swallowRelease) QLabel::mouseReleaseEvent(e);
+    m_swallowRelease = false;
+}
+
+void
+SelectableLabel::enterEvent(QEvent *)
+{
+//    std::cerr << "enterEvent" << std::endl;
+//    QPalette palette = QApplication::palette();
+//    palette.setColor(QPalette::Window, Qt::gray);
+//    setStyleSheet("background: gray");
+//    setPalette(palette);
+}
+
+void
+SelectableLabel::leaveEvent(QEvent *)
+{
+//    std::cerr << "leaveEvent" << std::endl;
+//    setStyleSheet("background: white");
+//    QPalette palette = QApplication::palette();
+//    palette.setColor(QPalette::Window, Qt::gray);
+//    setPalette(palette);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/selectablelabel.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,57 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _SELECTABLE_LABEL_H_
+#define _SELECTABLE_LABEL_H_
+
+#include <QLabel>
+
+class SelectableLabel : public QLabel
+{
+    Q_OBJECT
+
+public:
+    SelectableLabel(QWidget *parent = 0);
+    virtual ~SelectableLabel();
+
+    void setSelectedText(QString);
+    void setUnselectedText(QString);
+
+    bool isSelected() const { return m_selected; }
+
+signals:
+    void selectionChanged();
+    void doubleClicked();
+
+public slots:
+    void setSelected(bool);
+    void toggle();
+
+protected:
+    virtual void mousePressEvent(QMouseEvent *e);
+    virtual void mouseReleaseEvent(QMouseEvent *e);
+    virtual void mouseDoubleClickEvent(QMouseEvent *e);
+    virtual void enterEvent(QEvent *);
+    virtual void leaveEvent(QEvent *);
+    void setupStyle();
+    QString m_selectedText;
+    QString m_unselectedText;
+    bool m_selected;
+    bool m_swallowRelease;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/settingsdialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,528 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "settingsdialog.h"
+#include "common.h"
+#include "debug.h"
+
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QDialogButtonBox>
+#include <QSettings>
+#include <QDir>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QTabWidget>
+#include <QCalendarWidget>
+
+QString
+SettingsDialog::m_installPath;
+
+SettingsDialog::SettingsDialog(QWidget *parent) :
+    QDialog(parent),
+    m_presentationChanged(false)
+{
+    setModal(true);
+    setWindowTitle(tr("Settings"));
+
+    QGridLayout *mainLayout = new QGridLayout;
+    setLayout(mainLayout);
+
+    m_tabs = new QTabWidget;
+    mainLayout->addWidget(m_tabs, 0, 0);
+
+
+//    QGroupBox *meBox = new QGroupBox(tr("User details"));
+//    mainLayout->addWidget(meBox, 0, 0);
+
+    QWidget *meBox = new QWidget;
+    m_tabs->addTab(meBox, tr("User details"));
+
+    QGridLayout *meLayout = new QGridLayout;
+    meBox->setLayout(meLayout);
+
+    int row = 0;
+
+    meLayout->addWidget(new QLabel(tr("Name:")), row, 0);
+
+    m_nameEdit = new QLineEdit();
+    meLayout->addWidget(m_nameEdit, row++, 1);
+    
+    meLayout->addWidget(new QLabel(tr("Email address:")), row, 0);
+
+    m_emailEdit = new QLineEdit();
+    meLayout->addWidget(m_emailEdit, row++, 1);
+
+    meLayout->setRowStretch(row, 20);
+
+
+//    QGroupBox *lookBox = new QGroupBox(tr("Presentation"));
+//    mainLayout->addWidget(lookBox, 1, 0);
+    
+    QWidget *lookBox = new QWidget;
+    m_tabs->addTab(lookBox, tr("Presentation"));
+
+    QGridLayout *lookLayout = new QGridLayout;
+    lookBox->setLayout(lookLayout);
+
+    row = 0;
+
+    m_showIconLabels = new QCheckBox(tr("Show labels on toolbar icons"));
+    lookLayout->addWidget(m_showIconLabels, row++, 0, 1, 2);
+
+    m_showExtraText = new QCheckBox(tr("Show long descriptions for file status headings"));
+    lookLayout->addWidget(m_showExtraText, row++, 0, 1, 2);
+    
+    m_showHistoryAutomatically = new QCheckBox(tr("Switch to history tab automatically when history changes"));
+    lookLayout->addWidget(m_showHistoryAutomatically, row++, 0, 1, 2);
+
+#ifdef NOT_IMPLEMENTED_YET
+    lookLayout->addWidget(new QLabel(tr("Place the work and history views")), row, 0);
+    m_workHistoryArrangement = new QComboBox();
+    m_workHistoryArrangement->addItem(tr("In separate tabs"));
+    m_workHistoryArrangement->addItem(tr("Side-by-side in a single pane"));
+    lookLayout->addWidget(m_workHistoryArrangement, row++, 1, Qt::AlignLeft);
+    lookLayout->setColumnStretch(1, 20);
+#endif
+
+    lookLayout->addWidget(new QLabel(tr("History timeline from")), row, 0);
+    m_dateFrom = new QDateEdit();
+    m_dateFrom->setCalendarPopup(true);
+    lookLayout->addWidget(m_dateFrom, row++, 1, Qt::AlignLeft);
+
+    lookLayout->addWidget(new QLabel(tr("Label the history timeline with")), row, 0);
+    m_dateFormat = new QComboBox();
+    m_dateFormat->addItem(tr("Ages, for example \"5 weeks ago\""));
+    m_dateFormat->addItem(tr("Dates, for example \"2010-06-23\""));
+    lookLayout->addWidget(m_dateFormat, row++, 1, Qt::AlignLeft);
+
+    lookLayout->setColumnStretch(1, 10);
+    lookLayout->setRowStretch(row, 20);
+    
+
+    QWidget *pathsBox = new QWidget;
+    m_tabs->addTab(pathsBox, tr("System applications"));
+
+    QGridLayout *pathsLayout = new QGridLayout;
+    pathsBox->setLayout(pathsLayout);
+
+    row = 0;
+
+    pathsLayout->addWidget(new QLabel(tr("Mercurial (hg) program:")), row, 0);
+
+    m_hgPathLabel = new QLineEdit();
+    pathsLayout->addWidget(m_hgPathLabel, row, 2);
+
+    QPushButton *browse = new QPushButton(tr("Browse..."));
+    pathsLayout->addWidget(browse, row++, 1);
+    connect(browse, SIGNAL(clicked()), this, SLOT(hgPathBrowse()));
+
+    pathsLayout->addWidget(new QLabel(tr("External diff program:")), row, 0);
+
+    m_diffPathLabel = new QLineEdit();
+    pathsLayout->addWidget(m_diffPathLabel, row, 2);
+
+    browse = new QPushButton(tr("Browse..."));
+    pathsLayout->addWidget(browse, row++, 1);
+    connect(browse, SIGNAL(clicked()), this, SLOT(diffPathBrowse()));
+
+    m_multipleDiffInstances = new QCheckBox(tr("Run multiple instances for multiple files"));
+    pathsLayout->addWidget(m_multipleDiffInstances, row++, 2);
+
+    pathsLayout->addWidget(new QLabel(tr("External merge program:")), row, 0);
+
+    m_mergePathLabel = new QLineEdit();
+    pathsLayout->addWidget(m_mergePathLabel, row, 2);
+
+    browse = new QPushButton(tr("Browse..."));
+    pathsLayout->addWidget(browse, row++, 1);
+    connect(browse, SIGNAL(clicked()), this, SLOT(mergePathBrowse()));
+
+    pathsLayout->addWidget(new QLabel(tr("SSH program (for ssh URLs):")), row, 0);
+
+    m_sshPathLabel = new QLineEdit();
+    pathsLayout->addWidget(m_sshPathLabel, row, 2);
+
+    browse = new QPushButton(tr("Browse..."));
+    pathsLayout->addWidget(browse, row++, 1);
+    connect(browse, SIGNAL(clicked()), this, SLOT(sshPathBrowse()));
+
+
+    QWidget *extBox = new QWidget;
+    m_tabs->addTab(extBox, tr("Extensions"));
+
+    QGridLayout *extLayout = new QGridLayout;
+    extBox->setLayout(extLayout);
+
+    row = 0;
+
+    m_useExtension = new QCheckBox(tr("Use EasyHg Mercurial extension"));
+    extLayout->addWidget(m_useExtension, row++, 0, 1, 2);
+    extLayout->addWidget(new QLabel(tr("This extension may be required for password entry when using https\nprotocols. It also provides the \"remember password\" feature.")), row++, 0, 1, 3);
+
+    extLayout->addWidget(new QLabel(tr("Extension file path:")), row, 0);
+
+    m_extensionPathLabel = new QLineEdit();
+    extLayout->addWidget(m_extensionPathLabel, row, 2);
+
+    browse = new QPushButton(tr("Browse..."));
+    extLayout->addWidget(browse, row++, 1);
+    connect(browse, SIGNAL(clicked()), this, SLOT(extensionPathBrowse()));
+
+    extLayout->setRowStretch(row, 20);
+    extLayout->setColumnStretch(2, 20);
+
+
+    reset(); // loads current defaults from settings
+
+
+    QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok);
+    connect(bbox->addButton(tr("Restore defaults"), QDialogButtonBox::ResetRole),
+            SIGNAL(clicked()), this, SLOT(restoreDefaults()));
+    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
+    mainLayout->addWidget(bbox, 3, 0);
+    m_ok = bbox->button(QDialogButtonBox::Ok);
+}
+
+void
+SettingsDialog::setCurrentTab(Tab t)
+{
+    switch (t) {
+    case PersonalDetailsTab: m_tabs->setCurrentIndex(0); break;
+    case PresentationTab: m_tabs->setCurrentIndex(1); break;
+    case PathsTab: m_tabs->setCurrentIndex(2); break;
+    case ExtensionsTab: m_tabs->setCurrentIndex(3); break;
+    }
+}
+
+void
+SettingsDialog::hgPathBrowse()
+{
+    browseFor(tr("Mercurial program"), m_hgPathLabel);
+}
+
+void
+SettingsDialog::diffPathBrowse()
+{
+    browseFor(tr("External diff program"), m_diffPathLabel);
+}
+
+void
+SettingsDialog::mergePathBrowse()
+{
+    browseFor(tr("External file-merge program"), m_mergePathLabel);
+}
+
+void
+SettingsDialog::sshPathBrowse()
+{
+    browseFor(tr("SSH program"), m_sshPathLabel);
+}
+
+void
+SettingsDialog::extensionPathBrowse()
+{
+    browseFor(tr("EasyMercurial extension"), m_extensionPathLabel);
+}
+
+void
+SettingsDialog::browseFor(QString title, QLineEdit *edit)
+{
+    QString origin = edit->text();
+
+    if (origin == "") {
+#ifdef Q_OS_WIN32
+        origin = "c:";
+#else
+        origin = QDir::homePath();
+#endif
+    }
+    
+    QString path = QFileDialog::getOpenFileName(this, title, origin);
+    if (path != QString()) {
+        edit->setText(path);
+    }
+}
+
+void
+SettingsDialog::restoreDefaults()
+{
+    if (QMessageBox::question
+        (this, tr("Restore default settings?"),
+         tr("<qt><b>Restore default settings?</b><br><br>Are you sure you want to reset all settings to their default values?"),
+         QMessageBox::Ok | QMessageBox::Cancel,
+         QMessageBox::Cancel) == QMessageBox::Ok) {
+        clear();
+        findDefaultLocations();
+        reset();
+    }
+}
+
+void
+SettingsDialog::findDefaultLocations(QString installPath)
+{
+    m_installPath = installPath;
+    findHgBinaryName();
+    findExtension();
+    findDiffBinaryName();
+    findMergeBinaryName();
+    findSshBinaryName();
+}
+
+void
+SettingsDialog::findHgBinaryName()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    QString hg = settings.value("hgbinary", "").toString();
+    if (hg == "" || !QFile(hg).exists()) {
+        hg = findInPath("hg", m_installPath, true);
+    }
+    if (hg != "") {
+        settings.setValue("hgbinary", hg);
+    }
+}
+
+QString
+SettingsDialog::getUnbundledExtensionFileName()
+{
+    QString home = QDir::homePath();
+    QString target = QString("%1/.easyhg").arg(home);
+    QString extpath = QString("%1/easyhg.py").arg(target);
+    return extpath;
+}
+
+void
+SettingsDialog::findExtension()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+
+    QString extpath = settings.value("extensionpath", "").toString();
+    if (extpath != "" || !QFile(extpath).exists()) {
+
+        extpath = getUnbundledExtensionFileName();
+
+        if (!QFile(extpath).exists()) {
+            extpath = findInPath("easyhg.py", m_installPath, false);
+        }
+    }
+
+    settings.setValue("extensionpath", extpath);
+}   
+
+void
+SettingsDialog::findDiffBinaryName()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    QString diff = settings.value("extdiffbinary", "").toString();
+    if (diff != "" && QFile(diff).exists()) {
+        return;
+    }
+    QStringList bases;
+#ifdef Q_OS_WIN32
+    bases << "easyhg-extdiff.bat";
+#else
+    bases << "easyhg-extdiff.sh";
+#endif
+    bases << "kompare" << "kdiff3" << "meld";
+    bool found = false;
+    foreach (QString base, bases) {
+        diff = findInPath(base, m_installPath, true);
+        if (diff != "") {
+            found = true;
+            break;
+        }
+    }
+    if (found) {
+        settings.setValue("extdiffbinary", diff);
+    }
+}
+
+void
+SettingsDialog::findMergeBinaryName()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    QString merge = settings.value("mergebinary", "").toString();
+    if (merge != "" && QFile(merge).exists()) {
+        return;
+    }
+    QStringList bases;
+#ifdef Q_OS_WIN32
+    bases << "easyhg-merge.bat";
+#else
+    bases << "easyhg-merge.sh";
+#endif
+    // NB it's not a good idea to add other tools here, as command
+    // line argument ordering varies.  Configure them through hgrc
+    // instead
+    bool found = false;
+    foreach (QString base, bases) {
+        merge = findInPath(base, m_installPath, true);
+        if (merge != "") {
+            found = true;
+            break;
+        }
+    }
+    if (found) {
+        settings.setValue("mergebinary", merge);
+    }
+}
+
+void
+SettingsDialog::findSshBinaryName()
+{
+    QSettings settings;
+    settings.beginGroup("Locations");
+    QString ssh = settings.value("sshbinary", "").toString();
+    if (ssh != "" && QFile(ssh).exists()) {
+        return;
+    }
+    QStringList bases;
+#ifdef Q_OS_WIN32
+    bases << "TortoisePlink.exe";
+#else
+    bases << "ssh";
+#endif
+    bool found = false;
+    foreach (QString base, bases) {
+        ssh = findInPath(base, m_installPath, true);
+        if (ssh != "") {
+            found = true;
+            break;
+        }
+    }
+    if (found) {
+        settings.setValue("sshbinary", ssh);
+    }
+}
+
+void
+SettingsDialog::clear()
+{
+    // Clear everything that has a default setting
+    DEBUG << "SettingsDialog::clear" << endl;
+    QSettings settings;
+    settings.beginGroup("Presentation");
+    settings.remove("showiconlabels");
+    settings.remove("showhelpfultext");
+    settings.remove("showHistoryAutomatically");
+    settings.remove("dateformat");
+    settings.endGroup();
+    settings.beginGroup("Locations");
+    settings.remove("hgbinary");
+    settings.remove("extdiffbinary");
+    settings.remove("mergebinary");
+    settings.remove("sshbinary");
+    settings.remove("extensionpath");
+    settings.endGroup();
+    settings.beginGroup("");
+    settings.remove("useextension");
+    settings.remove("multipleDiffInstances");
+    settings.endGroup();
+}
+
+void
+SettingsDialog::reset()
+{
+    DEBUG << "SettingsDialog::reset" << endl;
+    QSettings settings;
+    settings.beginGroup("User Information");
+    m_nameEdit->setText(settings.value("name", getUserRealName()).toString());
+    m_emailEdit->setText(settings.value("email").toString());
+    settings.endGroup();
+    settings.beginGroup("Presentation");
+    m_showIconLabels->setChecked(settings.value("showiconlabels", true).toBool());
+    m_showExtraText->setChecked(settings.value("showhelpfultext", true).toBool());
+    m_showHistoryAutomatically->setChecked(settings.value("showHistoryAutomatically", true).toBool());
+#ifdef NOT_IMPLEMENTED_YET
+    m_workHistoryArrangement->setCurrentIndex(settings.value("workhistoryarrangement", 0).toInt());
+#endif
+    m_dateFormat->setCurrentIndex(settings.value("dateformat", 0).toInt());
+    m_dateFrom->setDate(settings.value("datefrom", QDate(2000, 1, 1)).toDate());
+    settings.endGroup();
+    settings.beginGroup("Locations");
+    m_hgPathLabel->setText(settings.value("hgbinary").toString());
+    m_diffPathLabel->setText(settings.value("extdiffbinary").toString());
+    m_mergePathLabel->setText(settings.value("mergebinary").toString());
+    m_sshPathLabel->setText(settings.value("sshbinary").toString());
+    m_extensionPathLabel->setText(settings.value("extensionpath").toString());
+    settings.endGroup();
+    settings.beginGroup("");
+    m_useExtension->setChecked(settings.value("useextension", true).toBool());
+    m_multipleDiffInstances->setChecked(settings.value("multipleDiffInstances", false).toBool());
+    settings.endGroup();
+}
+
+void
+SettingsDialog::accept()
+{
+    DEBUG << "SettingsDialog::accept" << endl;
+    QSettings settings;
+    settings.beginGroup("User Information");
+    settings.setValue("name", m_nameEdit->text());
+    settings.setValue("email", m_emailEdit->text());
+    settings.endGroup();
+    settings.beginGroup("Presentation");
+    bool b;
+    b = m_showIconLabels->isChecked();
+    if (b != settings.value("showiconlabels", true)) {
+        settings.setValue("showiconlabels", b);
+        m_presentationChanged = true;
+    }
+    b = m_showExtraText->isChecked();
+    if (b != settings.value("showhelpfultext", true)) {
+        settings.setValue("showhelpfultext", b);
+        m_presentationChanged = true;
+    }
+    settings.setValue("showHistoryAutomatically", m_showHistoryAutomatically->isChecked());
+    int i;
+#ifdef NOT_IMPLEMENTED_YET
+    i = m_workHistoryArrangement->currentIndex();
+    if (i != settings.value("workhistoryarrangement", 0)) {
+        settings.setValue("workhistoryarrangement", i);
+        m_presentationChanged = true;
+    }
+#endif
+    i = m_dateFormat->currentIndex();
+    if (i != settings.value("dateformat", 0)) {
+        settings.setValue("dateformat", i);
+        m_presentationChanged = true;
+    }
+    if(settings.value("datefrom") != m_dateFrom->date()){
+        settings.setValue("datefrom", m_dateFrom->date());
+        m_presentationChanged = true;
+    }
+
+    settings.endGroup();
+    settings.beginGroup("Locations");
+    settings.setValue("hgbinary", m_hgPathLabel->text());
+    settings.setValue("extdiffbinary", m_diffPathLabel->text());
+    settings.setValue("mergebinary", m_mergePathLabel->text());
+    settings.setValue("sshbinary", m_sshPathLabel->text());
+    settings.setValue("extensionpath", m_extensionPathLabel->text());
+    settings.endGroup();
+    settings.beginGroup("");
+    settings.setValue("useextension", m_useExtension->isChecked());
+    settings.setValue("multipleDiffInstances", m_multipleDiffInstances->isChecked());
+    settings.endGroup();
+    QDialog::accept();
+}
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/settingsdialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,107 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 SETTINGS_DIALOG_H
+#define SETTINGS_DIALOG_H
+
+#include <QDialog>
+#include <QLineEdit>
+#include <QLabel>
+#include <QPushButton>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QTabWidget>
+#include <QDateEdit>
+
+class SettingsDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    enum Tab {
+        PersonalDetailsTab,
+        PresentationTab,
+        PathsTab,
+        ExtensionsTab
+    };
+
+    SettingsDialog(QWidget *parent = 0);
+
+    void setCurrentTab(Tab tab);
+
+    bool presentationChanged() {
+        return m_presentationChanged;
+    }
+
+    static void findDefaultLocations(QString installPath = m_installPath);
+    static QString getUnbundledExtensionFileName();
+    
+private slots:
+    void hgPathBrowse();
+    void diffPathBrowse();
+    void mergePathBrowse();
+    void sshPathBrowse();
+    void extensionPathBrowse();
+
+    void accept();
+    void reset();
+    void clear();
+    void restoreDefaults();
+
+private:
+    QTabWidget *m_tabs;
+
+    QLineEdit *m_nameEdit;
+    QLineEdit *m_emailEdit;
+    QLineEdit *m_hgPathLabel;
+    QLineEdit *m_diffPathLabel;
+    QLineEdit *m_mergePathLabel;
+    QLineEdit *m_sshPathLabel;
+
+    QPushButton *m_extensionBrowse;
+
+    QCheckBox *m_multipleDiffInstances;
+    QCheckBox *m_useExtension;
+    QLineEdit *m_extensionPathLabel;
+
+    QCheckBox *m_showIconLabels;
+    QCheckBox *m_showExtraText;
+    QCheckBox *m_showHistoryAutomatically;
+    QComboBox *m_dateFormat;
+
+    QDateEdit *m_dateFrom;
+
+#ifdef NOT_IMPLEMENTED_YET
+    QComboBox *m_workHistoryArrangement;
+#endif
+
+    QPushButton *m_ok;
+
+    bool m_presentationChanged;
+
+    void browseFor(QString, QLineEdit *);
+
+    static void findHgBinaryName();
+    static void findExtension();
+    static void findDiffBinaryName();
+    static void findMergeBinaryName();
+    static void findSshBinaryName();
+
+    static QString m_installPath;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/squeezedlabel.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,180 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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.
+*/
+
+/*
+    This file adapted from Rosegarden, a sequencer and musical
+    notation editor.  Copyright 2000-2012 the Rosegarden development
+    team.
+
+    Adapted from KDE 4.2.0, this code originally Copyright (c) 2000
+    Ronny Standtke.
+*/
+
+#include "squeezedlabel.h"
+
+#include <iostream>
+
+#include <QContextMenuEvent>
+#include <QAction>
+#include <QMenu>
+#include <QClipboard>
+#include <QApplication>
+#include <QMimeData>
+#include <QDesktopWidget>
+
+
+class SqueezedLabelPrivate
+{
+public:
+};
+
+void SqueezedLabel::_k_copyFullText()
+{
+    QMimeData* data = new QMimeData;
+    data->setText(fullText);
+    QApplication::clipboard()->setMimeData(data);
+}
+
+SqueezedLabel::SqueezedLabel(const QString &text , QWidget *parent)
+        : QLabel (parent)
+{
+    setObjectName("SQUEEZED");
+    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
+    fullText = text;
+    elideMode = Qt::ElideMiddle;
+    squeezeTextToLabel();
+}
+
+SqueezedLabel::SqueezedLabel(QWidget *parent)
+        : QLabel (parent)
+{
+    setObjectName("SQUEEZED");
+    setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
+    elideMode = Qt::ElideMiddle;
+}
+
+SqueezedLabel::~SqueezedLabel()
+{
+}
+
+void SqueezedLabel::resizeEvent(QResizeEvent *)
+{
+    squeezeTextToLabel();
+}
+
+QSize SqueezedLabel::minimumSizeHint() const
+{
+    QSize sh = QLabel::minimumSizeHint();
+    sh.setWidth(-1);
+    return sh;
+}
+
+QSize SqueezedLabel::sizeHint() const
+{
+    int dw = QApplication::desktop()->availableGeometry(QPoint(0, 0)).width();
+    int maxWidth = dw * 3 / 4;
+    QFontMetrics fm(fontMetrics());
+    int textWidth = fm.width(fullText);
+    if (textWidth > maxWidth) {
+        textWidth = maxWidth;
+    }
+    return QSize(textWidth, QLabel::sizeHint().height());
+}
+
+void SqueezedLabel::setText(const QString &text)
+{
+    fullText = text;
+    squeezeTextToLabel();
+}
+
+void SqueezedLabel::clear() {
+    fullText.clear();
+    QLabel::clear();
+}
+
+void SqueezedLabel::squeezeTextToLabel() {
+    QFontMetrics fm(fontMetrics());
+    int labelWidth = size().width();
+    QStringList squeezedLines;
+    bool squeezed = false;
+    Q_FOREACH(const QString& line, fullText.split('\n')) {
+        int lineWidth = fm.width(line);
+        if (lineWidth > labelWidth) {
+            squeezed = true;
+            squeezedLines << fm.elidedText(line, elideMode, labelWidth);
+        } else {
+            squeezedLines << line;
+        }
+    }
+
+    if (squeezed) {
+        QLabel::setText(squeezedLines.join("\n"));
+        setToolTip(fullText);
+    } else {
+        QLabel::setText(fullText);
+        setToolTip(QString());
+    }
+}
+
+void SqueezedLabel::setAlignment(Qt::Alignment alignment)
+{
+    // save fullText and restore it
+    QString tmpFull(fullText);
+    QLabel::setAlignment(alignment);
+    fullText = tmpFull;
+}
+
+Qt::TextElideMode SqueezedLabel::textElideMode() const
+{
+    return elideMode;
+}
+
+void SqueezedLabel::setTextElideMode(Qt::TextElideMode mode)
+{
+    elideMode = mode;
+    squeezeTextToLabel();
+}
+
+void SqueezedLabel::contextMenuEvent(QContextMenuEvent* ev)
+{
+    // "We" means the KDE team here.
+    //
+    // We want to reimplement "Copy" to include the elided text.
+    // But this means reimplementing the full popup menu, so no more
+    // copy-link-address or copy-selection support anymore, since we
+    // have no access to the QTextDocument.
+    // Maybe we should have a boolean flag in SqueezedLabel itself for
+    // whether to show the "Copy Full Text" custom popup?
+    // For now I chose to show it when the text is squeezed; when it's not, the
+    // standard popup menu can do the job (select all, copy).
+
+    const bool squeezed = text() != fullText;
+    const bool showCustomPopup = squeezed;
+    if (showCustomPopup) {
+        QMenu menu(this);
+
+        QAction* act = new QAction(tr("&Copy Full Text"), this);
+        connect(act, SIGNAL(triggered()), this, SLOT(_k_copyFullText()));
+        menu.addAction(act);
+
+        ev->accept();
+        menu.exec(ev->globalPos());
+    } else {
+        QLabel::contextMenuEvent(ev);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/squeezedlabel.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,120 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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.
+*/
+
+/*
+    This file adapted from Rosegarden, a sequencer and musical
+    notation editor.  Copyright 2000-2012 the Rosegarden development
+    team.
+
+    Adapted from KDE 4.2.0, this code originally Copyright (c) 2000
+    Ronny Standtke.
+*/
+
+#ifndef _SQUEEZED_LABEL_H_
+#define _SQUEEZED_LABEL_H_
+
+#include <QLabel>
+
+class SqueezedLabelPrivate;
+
+/**
+ * @short A replacement for QLabel that squeezes its text
+ *
+ * A label class that squeezes its text into the label
+ *
+ * If the text is too long to fit into the label it is divided into
+ * remaining left and right parts which are separated by three dots.
+ */
+
+class SqueezedLabel : public QLabel
+{
+    Q_OBJECT
+    Q_PROPERTY(Qt::TextElideMode textElideMode READ textElideMode WRITE setTextElideMode)
+
+public:
+    /**
+    * Default constructor.
+    */
+    explicit SqueezedLabel(QWidget *parent = 0);
+    explicit SqueezedLabel(const QString &text, QWidget *parent = 0);
+
+    virtual ~SqueezedLabel();
+
+    virtual QSize minimumSizeHint() const;
+    virtual QSize sizeHint() const;
+    /**
+    * Overridden for internal reasons; the API remains unaffected.
+    */
+    virtual void setAlignment(Qt::Alignment);
+
+    /**
+    *  Returns the text elide mode.
+    */
+    Qt::TextElideMode textElideMode() const;
+
+    /**
+    * Sets the text elide mode.
+    * @param mode The text elide mode.
+    */
+    void setTextElideMode(Qt::TextElideMode mode);
+
+public Q_SLOTS:
+    /**
+    * Sets the text. Note that this is not technically a reimplementation of QLabel::setText(),
+    * which is not virtual (in Qt 4.3). Therefore, you may need to cast the object to
+    * SqueezedLabel in some situations:
+    * \Example
+    * \code
+    * SqueezedLabel* squeezed = new SqueezedLabel("text", parent);
+    * QLabel* label = squeezed;
+    * label->setText("new text");    // this will not work
+    * squeezed->setText("new text");    // works as expected
+    * static_cast<SqueezedLabel*>(label)->setText("new text");    // works as expected
+    * \endcode
+    * @param mode The new text.
+    */
+    void setText(const QString &text);
+    /**
+    * Clears the text. Same remark as above.
+    *
+    */
+    void clear();
+
+protected:
+    /**
+    * Called when widget is resized
+    */
+    void resizeEvent(QResizeEvent *);
+    /**
+    * \reimp
+    */
+    void contextMenuEvent(QContextMenuEvent*);
+    /**
+    * does the dirty work
+    */
+    void squeezeTextToLabel();
+
+private slots:
+    void _k_copyFullText();
+
+private:
+    QString fullText;
+    Qt::TextElideMode elideMode;
+};
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/startupdialog.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,113 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "startupdialog.h"
+#include "common.h"
+
+#include <QGridLayout>
+#include <QDialogButtonBox>
+#include <QSettings>
+
+StartupDialog::StartupDialog(QWidget *parent) :
+    QDialog(parent)
+{
+    setModal(true);
+    setWindowTitle(tr("About me"));
+
+    QSettings settings;
+    settings.beginGroup("User Information");
+    m_name = settings.value("name", getUserRealName()).toString();
+    m_email = settings.value("email").toString();
+    
+    QGridLayout *layout = new QGridLayout;
+    int row = 0;
+
+    layout->addWidget(new QLabel(tr("<qt><big>Welcome to EasyMercurial!</qt></big><br>How would you like to be identified in commit messages?")),
+		      row++, 0, 1, 2);
+
+    layout->addWidget(new QLabel(tr("Name:")), row, 0);
+
+    m_nameEdit = new QLineEdit();
+    m_nameEdit->setText(m_name);
+    connect(m_nameEdit, SIGNAL(textChanged(const QString &)),
+	    this, SLOT(realNameChanged(const QString &)));
+    layout->addWidget(m_nameEdit, row++, 1);
+    
+    layout->addWidget(new QLabel(tr("Email address:")), row, 0);
+
+    m_emailEdit = new QLineEdit();
+    m_emailEdit->setText(m_email);
+    connect(m_emailEdit, SIGNAL(textChanged(const QString &)),
+	    this, SLOT(emailChanged(const QString &)));
+    layout->addWidget(m_emailEdit, row++, 1);
+
+    layout->addWidget(new QLabel(tr("<br>You will appear as:")), row, 0, Qt::AlignBottom);
+    m_example = new QLabel();
+    layout->addWidget(m_example, row++, 1, Qt::AlignBottom);
+
+    QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok);
+    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
+    layout->addWidget(bbox, row++, 0, 1, 2);
+    m_ok = bbox->button(QDialogButtonBox::Ok);
+    m_ok->setEnabled(false);
+    
+    setLayout(layout);
+
+    m_ok->setEnabled(m_name != "");
+    updateExample();
+}
+
+void
+StartupDialog::realNameChanged(const QString &s)
+{
+    m_name = s.trimmed();
+    m_ok->setEnabled(m_name != "");
+    updateExample();
+}
+
+void
+StartupDialog::emailChanged(const QString &s)
+{
+    m_email = s.trimmed();
+    updateExample();
+}
+
+void
+StartupDialog::accept()
+{
+    QSettings settings;
+    settings.beginGroup("User Information");
+    settings.setValue("name", m_name);
+    settings.setValue("email", m_email);
+    QDialog::accept();
+}
+
+void
+StartupDialog::updateExample()
+{
+    QString identifier;
+
+    if (m_email != "") {
+	identifier = QString("%1 <%2>").arg(m_name).arg(m_email);
+    } else {
+	identifier = m_name;
+    }
+
+    m_example->setText(identifier);
+}
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/startupdialog.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,50 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 STARTUP_DIALOG_H
+#define STARTUP_DIALOG_H
+
+#include <QDialog>
+#include <QLineEdit>
+#include <QLabel>
+#include <QPushButton>
+
+class StartupDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    StartupDialog(QWidget *parent = 0);
+    
+private slots:
+    void realNameChanged(const QString &);
+    void emailChanged(const QString &);
+    void accept();
+
+private:
+    QLineEdit *m_nameEdit;
+    QLineEdit *m_emailEdit;
+    QLabel *m_example;
+    QPushButton *m_ok;
+
+    QString m_name;
+    QString m_email;
+
+    void updateExample();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/textabbrev.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,341 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "textabbrev.h"
+#include "debug.h"
+
+#include <QFontMetrics>
+#include <QApplication>
+
+#include <iostream>
+
+QString
+TextAbbrev::getDefaultEllipsis()
+{
+    return "...";
+}
+
+int
+TextAbbrev::getFuzzLength(QString ellipsis)
+{
+    int len = ellipsis.length();
+    if (len < 3) return len + 3;
+    else if (len > 5) return len + 5;
+    else return len * 2;
+}
+
+int
+TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis)
+{
+    int width = metrics.width(ellipsis);
+    return width * 2;
+}
+
+QString
+TextAbbrev::abbreviateTo(QString text, int characters, Policy policy,
+                         QString ellipsis)
+{
+    switch (policy) {
+
+    case ElideEnd:
+    case ElideEndAndCommonPrefixes:
+        text = text.left(characters) + ellipsis;
+        break;
+        
+    case ElideStart:
+        text = ellipsis + text.right(characters);
+        break;
+
+    case ElideMiddle:
+        if (characters > 2) {
+            text = text.left(characters/2 + 1) + ellipsis
+                + text.right(characters - (characters/2 + 1));
+        } else {
+            text = text.left(characters) + ellipsis;
+        }
+        break;
+    }
+
+    return text;
+}
+
+QString
+TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy,
+                       QString ellipsis)
+{
+    if (ellipsis == "") ellipsis = getDefaultEllipsis();
+    int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
+    if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
+    if (text.length() <= maxLength + fl) return text;
+
+    int truncated = maxLength - ellipsis.length();
+    return abbreviateTo(text, truncated, policy, ellipsis);
+}
+
+QString
+TextAbbrev::abbreviate(QString text,
+                       const QFontMetrics &metrics, int &maxWidth,
+                       Policy policy, QString ellipsis, int wrapLines)
+{
+    if (ellipsis == "") ellipsis = getDefaultEllipsis();
+
+//    std::cerr << "TextAbbrev::abbreviate: text = " << text << std::endl;
+
+    int tw = metrics.width(text);
+    int tl = text.length();
+
+//    std::cerr << "\n*** TextAbbrev::abbreviate: tw = " << tw << "\n***\n" << std::endl;
+
+    if (tw <= maxWidth) {
+        maxWidth = tw;
+        return text;
+    }
+
+    QString original = text;
+
+    int acw = metrics.averageCharWidth();
+
+    if (wrapLines < 2) {
+
+//        std::cerr << "only " << wrapLines << " line(s) remaining, going by character..." << std::endl;
+
+        int truncateTo = tl;
+
+        while (tw > maxWidth && truncateTo > 1) {
+
+            if (tw > maxWidth + acw * (ellipsis.length() + 5)) {
+                float ratio = float(maxWidth) / float(tw);
+                truncateTo = int(truncateTo * ratio * 1.2) + 10;
+                if (truncateTo >= tl) truncateTo = tl - 1;
+            } else {
+                truncateTo--;
+            }
+            
+//            std::cerr << "truncating from " << tl << " to " << truncateTo << std::endl;
+
+            if (truncateTo > ellipsis.length()) {
+                text = abbreviateTo(original, truncateTo, policy, ellipsis);
+            } else {
+                break;
+            }
+            
+            tw = metrics.width(text);
+            tl = text.length() - ellipsis.length();
+        }
+
+//        std::cerr << "done, final tw = " << tw << std::endl;
+        
+        maxWidth = std::max(maxWidth, tw);
+        return text;
+
+    } else {
+        
+        QStringList words = text.split(' ', QString::SkipEmptyParts);
+        text = "";
+
+        tw = 0;
+        int i = 0;
+        QString good = "";
+        int lastUsed = 0;
+
+//        std::cerr << "have " << wrapLines << " lines remaining, going by word..." << std::endl;
+
+        while (tw < maxWidth && i < words.size()) {
+            if (text != "") text += " ";
+            text += words[i++];
+            tw = metrics.width(text);
+            if (tw < maxWidth) {
+                good = text;
+                lastUsed = i;
+            }
+        }
+
+//        std::cerr << "done" << std::endl;
+            
+        if (tw < maxWidth) {
+            maxWidth = tw;
+            return text;
+        }
+
+        text = good;
+
+        QString remainder;
+        while (lastUsed < words.size()) {
+            if (remainder != "") remainder += " ";
+            remainder += words[lastUsed++];
+        }
+        remainder = abbreviate(remainder, metrics, maxWidth,
+                               policy, ellipsis, wrapLines - 1);
+
+        maxWidth = std::max(maxWidth, tw);
+        text = text + '\n' + remainder;
+        return text;
+    }
+}
+
+QStringList
+TextAbbrev::abbreviate(const QStringList &texts, int maxLength,
+                       Policy policy, bool fuzzy, QString ellipsis)
+{
+    if (policy == ElideEndAndCommonPrefixes &&
+        texts.size() > 1) {
+
+        if (ellipsis == "") ellipsis = getDefaultEllipsis();
+        int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
+        if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
+
+        int maxOrigLength = 0;
+        for (int i = 0; i < texts.size(); ++i) {
+            int len = texts[i].length();
+            if (len > maxOrigLength) maxOrigLength = len;
+        }
+        if (maxOrigLength <= maxLength + fl) return texts;
+
+        return abbreviate(elidePrefixes
+                          (texts, maxOrigLength - maxLength, ellipsis),
+                          maxLength, ElideEnd, fuzzy, ellipsis);
+    }
+
+    QStringList results;
+    for (int i = 0; i < texts.size(); ++i) {
+        results.push_back
+            (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis));
+    }
+    return results;
+}
+
+QStringList
+TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics,
+                       int &maxWidth, Policy policy, QString ellipsis)
+{
+    if (policy == ElideEndAndCommonPrefixes &&
+        texts.size() > 1) {
+
+        if (ellipsis == "") ellipsis = getDefaultEllipsis();
+
+        int maxOrigWidth = 0;
+        for (int i = 0; i < texts.size(); ++i) {
+            int w = metrics.width(texts[i]);
+            if (w > maxOrigWidth) maxOrigWidth = w;
+        }
+
+        return abbreviate(elidePrefixes(texts, metrics,
+                                        maxOrigWidth - maxWidth, ellipsis),
+                          metrics, maxWidth, ElideEnd, ellipsis);
+    }
+
+    QStringList results;
+    int maxAbbrWidth = 0;
+    for (int i = 0; i < texts.size(); ++i) {
+        int width = maxWidth;
+        QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis);
+        if (width > maxAbbrWidth) maxAbbrWidth = width;
+        results.push_back(abbr);
+    }
+    maxWidth = maxAbbrWidth;
+    return results;
+}
+
+QStringList
+TextAbbrev::elidePrefixes(const QStringList &texts,
+                          int targetReduction,
+                          QString ellipsis)
+{
+    if (texts.empty()) return texts;
+    int plen = getPrefixLength(texts);
+    int fl = getFuzzLength(ellipsis);
+    if (plen < fl) return texts;
+
+    QString prefix = texts[0].left(plen);
+    int truncated = plen;
+    if (plen >= targetReduction + fl) {
+        truncated = plen - targetReduction;
+    } else {
+        truncated = fl;
+    }
+    prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis);
+
+    QStringList results;
+    for (int i = 0; i < texts.size(); ++i) {
+        results.push_back
+            (prefix + texts[i].right(texts[i].length() - plen));
+    }
+    return results;
+}
+
+QStringList
+TextAbbrev::elidePrefixes(const QStringList &texts,
+                          const QFontMetrics &metrics,
+                          int targetWidthReduction,
+                          QString ellipsis)
+{
+    if (texts.empty()) return texts;
+    int plen = getPrefixLength(texts);
+    int fl = getFuzzLength(ellipsis);
+    if (plen < fl) return texts;
+
+    QString prefix = texts[0].left(plen);
+    int pwid = metrics.width(prefix);
+    int twid = pwid - targetWidthReduction;
+    if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2;
+    prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis);
+
+    QStringList results;
+    for (int i = 0; i < texts.size(); ++i) {
+        results.push_back
+            (prefix + texts[i].right(texts[i].length() - plen));
+    }
+    return results;
+}
+
+static bool
+havePrefix(QString prefix, const QStringList &texts)
+{
+    for (int i = 1; i < texts.size(); ++i) {
+        if (!texts[i].startsWith(prefix)) return false;
+    }
+    return true;
+}
+
+int
+TextAbbrev::getPrefixLength(const QStringList &texts)
+{
+    QString reference = texts[0];
+
+    if (reference == "" || havePrefix(reference, texts)) {
+        return reference.length();
+    }
+
+    int candidate = reference.length();
+    QString splitChars(";:,./#-!()$_+=[]{}\\");
+
+    while (--candidate > 1) {
+        if (splitChars.contains(reference[candidate])) {
+            if (havePrefix(reference.left(candidate), texts)) {
+                break;
+            }
+        }
+    }
+
+//    std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl;
+//    for (int i = 0; i < texts.size(); ++i) {
+//        std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl;
+//    }
+
+    return candidate;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/textabbrev.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,111 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _TEXT_ABBREV_H_
+#define _TEXT_ABBREV_H_
+
+#include <QString>
+#include <QStringList>
+
+class QFontMetrics;
+
+class TextAbbrev 
+{
+public:
+    enum Policy {
+        ElideEnd,
+        ElideEndAndCommonPrefixes,
+        ElideStart,
+        ElideMiddle
+    };
+
+    /**
+     * Abbreviate the given text to the given maximum length
+     * (including ellipsis), using the given abbreviation policy.  If
+     * fuzzy is true, the text will be left alone if it is "not much
+     * more than" the maximum length.
+     * 
+     * If ellipsis is non-empty, it will be used to show elisions in
+     * preference to the default (which is "...").
+     */
+    static QString abbreviate(QString text, int maxLength,
+                              Policy policy = ElideEnd,
+                              bool fuzzy = true,
+                              QString ellipsis = "");
+
+    /**
+     * Abbreviate the given text to the given maximum painted width,
+     * using the given abbreviation policy.  maxWidth is also modified
+     * so as to return the painted width of the abbreviated text.
+     *
+     * If ellipsis is non-empty, it will be used to show elisions in
+     * preference to the default (which is tr("...")).
+     */
+    static QString abbreviate(QString text,
+                              const QFontMetrics &metrics,
+                              int &maxWidth,
+                              Policy policy = ElideEnd,
+                              QString ellipsis = "",
+                              int wrapLines = 1);
+    
+    /**
+     * Abbreviate all of the given texts to the given maximum length,
+     * using the given abbreviation policy.  If fuzzy is true, texts
+     * that are "not much more than" the maximum length will be left
+     * alone.
+     *
+     * If ellipsis is non-empty, it will be used to show elisions in
+     * preference to the default (which is tr("...")).
+     */
+    static QStringList abbreviate(const QStringList &texts, int maxLength,
+                                  Policy policy = ElideEndAndCommonPrefixes,
+                                  bool fuzzy = true,
+                                  QString ellipsis = "");
+
+    /**
+     * Abbreviate all of the given texts to the given maximum painted
+     * width, using the given abbreviation policy.  maxWidth is also
+     * modified so as to return the maximum painted width of the
+     * abbreviated texts.
+     *
+     * If ellipsis is non-empty, it will be used to show elisions in
+     * preference to the default (which is tr("...")).
+     */
+    static QStringList abbreviate(const QStringList &texts,
+                                  const QFontMetrics &metrics,
+                                  int &maxWidth,
+                                  Policy policy = ElideEndAndCommonPrefixes,
+                                  QString ellipsis = "");
+
+protected:
+    static QString getDefaultEllipsis();
+    static int getFuzzLength(QString ellipsis);
+    static int getFuzzWidth(const QFontMetrics &metrics, QString ellipsis);
+    static QString abbreviateTo(QString text, int characters,
+                                Policy policy, QString ellipsis);
+    static QStringList elidePrefixes(const QStringList &texts,
+                                     int targetReduction,
+                                     QString ellipsis);
+    static QStringList elidePrefixes(const QStringList &texts,
+                                     const QFontMetrics &metrics,
+                                     int targetWidthReduction,
+                                     QString ellipsis);
+    static int getPrefixLength(const QStringList &texts);
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/uncommitteditem.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,245 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "uncommitteditem.h"
+#include "colourset.h"
+#include "debug.h"
+#include "textabbrev.h"
+
+#include <QPainter>
+#include <QGraphicsScene>
+#include <QGraphicsSceneMouseEvent>
+#include <QMenu>
+#include <QAction>
+#include <QLabel>
+#include <QWidgetAction>
+
+UncommittedItem::UncommittedItem() :
+    m_showBranch(false), m_isNewBranch(false), m_isMerge(false),
+    m_column(0), m_row(0), m_wide(false)
+{
+    m_font = QFont();
+    m_font.setPixelSize(11);
+    m_font.setBold(false);
+    m_font.setItalic(false);
+    setCursor(Qt::ArrowCursor);
+}
+
+QRectF
+UncommittedItem::boundingRect() const
+{
+    //!!! this stuff is gross, refactor with changesetitem and connectionitem
+    int w = 100;
+    if (m_wide) w = 180;
+    return QRectF(-((w-50)/2 - 1), -30, w - 3, 79 + 40);
+}
+
+void
+UncommittedItem::mousePressEvent(QGraphicsSceneMouseEvent *e)
+{
+    DEBUG << "UncommittedItem::mousePressEvent" << endl;
+    if (e->button() == Qt::RightButton) {
+        activateMenu();
+    }
+}
+
+void
+UncommittedItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *e)
+{
+    DEBUG << "UncommittedItem::mouseDoubleClickEvent" << endl;
+    if (e->button() == Qt::LeftButton) {
+        emit showWork();
+    }
+}
+
+void
+UncommittedItem::activateMenu()
+{
+    QMenu *menu = new QMenu;
+    QLabel *label = new QLabel
+        (m_isMerge ?
+         tr("<qt><b>&nbsp;Uncommitted merge</b></qt>") :
+         tr("<qt><b>&nbsp;Uncommitted changes</b></qt>"));
+    QWidgetAction *wa = new QWidgetAction(menu);
+    wa->setDefaultWidget(label);
+    menu->addAction(wa);
+    menu->addSeparator();
+
+    QAction *dif = menu->addAction(tr("Diff"));
+    connect(dif, SIGNAL(triggered()), this, SIGNAL(diff()));
+    QAction *stat = menu->addAction(tr("Summarise changes"));
+    connect(stat, SIGNAL(triggered()), this, SIGNAL(showSummary()));
+    
+    menu->addSeparator();
+
+    QAction *commit = menu->addAction(tr("Commit..."));
+    connect(commit, SIGNAL(triggered()), this, SIGNAL(commit()));
+    QAction *revert = menu->addAction(tr("Revert..."));
+    connect(revert, SIGNAL(triggered()), this, SIGNAL(revert()));
+
+    menu->addSeparator();
+
+    QAction *branch = menu->addAction(tr("Start new branch..."));
+    connect(branch, SIGNAL(triggered()), this, SIGNAL(newBranch()));
+    QAction *nobranch = menu->addAction(tr("Cancel new branch"));
+    nobranch->setEnabled(m_isNewBranch);
+    connect(nobranch, SIGNAL(triggered()), this, SIGNAL(noBranch()));
+
+    menu->exec(QCursor::pos());
+
+    ungrabMouse();
+}
+
+void
+UncommittedItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *)
+{
+    if (isMerge()) {
+        paintMerge(paint);
+    } else {
+        paintNormal(paint);
+    }
+}
+
+void
+UncommittedItem::paintNormal(QPainter *paint)
+{
+    paint->save();
+    
+    ColourSet *colourSet = ColourSet::instance();
+    QColor branchColour = colourSet->getColourFor(m_branch);
+
+    QFont f(m_font);
+
+    QTransform t = paint->worldTransform();
+    float scale = std::min(t.m11(), t.m22());
+    if (scale > 1.0) {
+	int ps = int((f.pixelSize() / scale) + 0.5);
+	if (ps < 8) ps = 8;
+	f.setPixelSize(ps);
+    }
+
+    if (scale < 0.1) {
+	paint->setPen(QPen(branchColour, 0, Qt::DashLine));
+    } else {
+	paint->setPen(QPen(branchColour, 2, Qt::DashLine));
+    }
+	
+    paint->setFont(f);
+    QFontMetrics fm(f);
+    int fh = fm.height();
+
+    int width = 100;
+    if (m_wide) width = 180;
+    int x0 = -((width - 50) / 2 - 1);
+
+    int height = 49;
+    QRectF r(x0, 0, width - 3, height);
+    paint->setBrush(Qt::white);
+    paint->drawRoundedRect(r, 7, 7);
+
+    if (m_wide) {
+        QString label = tr("Uncommitted changes");
+        paint->drawText(-(fm.width(label) - 50)/2,
+                        25 - fm.height()/2 + fm.ascent(),
+                        label);
+    } else {
+        QString label = tr("Uncommitted");
+        paint->drawText(-(fm.width(label) - 50)/2,
+                        25 - fm.height() + fm.ascent(),
+                        label);
+        label = tr("changes");
+        paint->drawText(-(fm.width(label) - 50)/2,
+                        25 + fm.ascent(),
+                        label);
+    }        
+
+    if (m_showBranch && m_branch != "") {
+        // write branch name
+        f.setBold(true);
+        paint->setFont(f);
+        int wid = width - 3;
+        QString b = TextAbbrev::abbreviate(m_branch, QFontMetrics(f), wid);
+        paint->drawText(x0, -fh + fm.ascent() - 4, b);
+        f.setBold(false);
+    }
+
+    paint->restore();
+    return;
+}
+
+void
+UncommittedItem::paintMerge(QPainter *paint)
+{
+    paint->save();
+    
+    ColourSet *colourSet = ColourSet::instance();
+    QColor branchColour = colourSet->getColourFor(m_branch);
+
+    QFont f(m_font);
+
+    QTransform t = paint->worldTransform();
+    float scale = std::min(t.m11(), t.m22());
+    if (scale > 1.0) {
+	int ps = int((f.pixelSize() / scale) + 0.5);
+	if (ps < 8) ps = 8;
+	f.setPixelSize(ps);
+    }
+
+    if (scale < 0.1) {
+	paint->setPen(QPen(branchColour, 0, Qt::DashLine));
+    } else {
+	paint->setPen(QPen(branchColour, 2, Qt::DashLine));
+    }
+	
+    paint->setFont(f);
+    QFontMetrics fm(f);
+    int fh = fm.height();
+
+    int size = fh * 2;
+    int x0 = -size/2 + 25;
+
+    paint->setBrush(Qt::white);
+    paint->drawEllipse(QRectF(x0, fh, size, size));
+    
+    if (m_wide) {
+        QString label = tr("Uncommitted merge");
+        paint->drawText(size/2 + 28,
+                        25 - fm.height()/2 + fm.ascent(),
+                        label);
+    } else {
+        QString label = tr("Uncommitted");
+        paint->drawText(size/2 + 28,
+                        25 - fm.height() + fm.ascent(),
+                        label);
+        label = tr("merge");
+        paint->drawText(size/2 + 28,
+                        25 + fm.ascent(),
+                        label);
+    }        
+
+    if (m_showBranch && m_branch != "") {
+        // write branch name
+        f.setBold(true);
+        paint->setFont(f);
+	int wid = size * 3;
+	QString branch = TextAbbrev::abbreviate(m_branch, QFontMetrics(f), wid);
+	paint->drawText(-wid/2 + 25, fm.ascent() - 4, branch);
+    }
+
+    paint->restore();
+    return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/uncommitteditem.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,83 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 UNCOMMITTEDITEM_H
+#define UNCOMMITTEDITEM_H
+
+#include <QGraphicsObject>
+#include <QFont>
+
+class UncommittedItem : public QGraphicsObject
+{
+    Q_OBJECT
+
+public:
+    UncommittedItem();
+
+    virtual QRectF boundingRect() const;
+    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
+
+    QString branch() const { return m_branch; }
+    void setBranch(QString b) { m_branch = b; }
+
+    bool showBranch() const { return m_showBranch; }
+    void setShowBranch(bool s) { m_showBranch = s; }
+
+    bool isNewBranch() const { return m_isNewBranch; }
+    void setIsNewBranch(bool s) { m_isNewBranch = s; }
+
+    bool isMerge() const { return m_isMerge; }
+    void setIsMerge(bool m) { m_isMerge = m; }
+    
+    int column() const { return m_column; }
+    int row() const { return m_row; }
+    void setColumn(int c) { m_column = c; setX(c * 100); }
+    void setRow(int r) { m_row = r; setY(r * 90); }
+
+    bool isWide() const { return m_wide; }
+    void setWide(bool w) { m_wide = w; }
+
+signals:
+    void commit();
+    void revert();
+    void diff();
+    void showSummary();
+    void showWork();
+    void newBranch();
+    void noBranch();
+
+protected:
+    virtual void mousePressEvent(QGraphicsSceneMouseEvent *);
+    virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *);
+
+private:
+    void activateMenu();
+
+    QString m_branch;
+    bool m_showBranch;
+    bool m_isNewBranch;
+    bool m_isMerge;
+    QFont m_font;
+    int m_column;
+    int m_row;
+    bool m_wide;
+
+    void paintNormal(QPainter *);
+    void paintMerge(QPainter *);
+};
+
+#endif // UNCOMMITTEDITEM_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/version.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,1 @@
+#define EASYHG_VERSION "1.4"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/versiontester.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,106 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "versiontester.h"
+#include "debug.h"
+
+#include <iostream>
+
+#include <QNetworkAccessManager>
+
+static QNetworkAccessManager nm;
+
+VersionTester::VersionTester(QString hostname, QString versionFilePath,
+			     QString myVersion) :
+    m_myVersion(myVersion),
+    m_reply(0),
+    m_httpFailed(false)
+{
+    QUrl url(QString("http://%1/%2").arg(hostname).arg(versionFilePath));
+    std::cerr << "VersionTester: URL is " << url << std::endl;
+    m_reply = nm.get(QNetworkRequest(url));
+    connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
+            this, SLOT(error(QNetworkReply::NetworkError)));
+    connect(m_reply, SIGNAL(finished()), this, SLOT(finished()));
+}
+
+VersionTester::~VersionTester()
+{
+    if (m_reply) {
+        m_reply->abort();
+        m_reply->deleteLater();
+    }
+}
+
+bool
+VersionTester::isVersionNewerThan(QString a, QString b)
+{
+    QRegExp re("[._-]");
+    QStringList alist = a.split(re, QString::SkipEmptyParts);
+    QStringList blist = b.split(re, QString::SkipEmptyParts);
+    int ae = alist.size();
+    int be = blist.size();
+    int e = std::max(ae, be);
+    for (int i = 0; i < e; ++i) {
+    int an = 0, bn = 0;
+    if (i < ae) {
+        an = alist[i].toInt();
+        if (an == 0) an = -1; // non-numeric field -> "-pre1" etc
+    }
+    if (i < be) {
+        bn = blist[i].toInt();
+        if (bn == 0) bn = -1;
+    }
+    if (an < bn) return false;
+    if (an > bn) return true;
+    }
+    return false;
+}
+
+void
+VersionTester::error(QNetworkReply::NetworkError)
+{
+    std::cerr << "VersionTester: error: " << m_reply->errorString() << std::endl;
+    m_httpFailed = true;
+}
+
+void
+VersionTester::finished()
+{
+    m_reply->deleteLater();
+    if (m_httpFailed) return;
+
+    int status = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+    if (status / 100 != 2) {
+        std::cerr << "VersionTester: error: http status = " << status << std::endl;
+        return;
+    }
+
+    QByteArray responseData = m_reply->readAll();
+    QString str = QString::fromUtf8(responseData.data());
+    QStringList lines = str.split('\n', QString::SkipEmptyParts);
+    if (lines.empty()) return;
+
+    QString latestVersion = lines[0];
+    DEBUG << "Comparing current version \"" << m_myVersion
+          << "\" with latest version \"" << latestVersion
+          << "\"" << endl;
+    if (isVersionNewerThan(latestVersion, m_myVersion)) {
+        emit newerVersionAvailable(latestVersion);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/versiontester.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,50 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on hgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 _VERSION_TESTER_H_
+#define _VERSION_TESTER_H_
+
+#include <QStringList>
+#include <QString>
+#include <QObject>
+#include <QNetworkReply>
+
+class VersionTester : public QObject
+{
+    Q_OBJECT
+
+public:
+    VersionTester(QString hostname, QString versionFilePath, QString myVersion);
+    virtual ~VersionTester();
+    
+    static bool isVersionNewerThan(QString, QString);
+
+signals:
+    void newerVersionAvailable(QString);
+
+protected slots:
+    void finished();
+    void error(QNetworkReply::NetworkError);
+
+private:
+    QString m_myVersion;
+    QNetworkReply *m_reply;
+    bool m_httpFailed;
+};
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/workstatuswidget.cpp	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,127 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 "workstatuswidget.h"
+#include "debug.h"
+#include "clickablelabel.h"
+#include "squeezedlabel.h"
+
+#include <QGridLayout>
+#include <QSpacerItem>
+#include <QLabel>
+#include <QProcess>
+#include <QDir>
+
+WorkStatusWidget::WorkStatusWidget(QWidget *parent) :
+    QWidget(parent)
+{
+    QGridLayout *layout = new QGridLayout;
+    layout->setMargin(6);
+    layout->setSpacing(6);
+    setLayout(layout);
+
+    int row = 0;
+
+#ifndef Q_OS_MAC    
+    layout->addItem(new QSpacerItem(1, 1), row, 0);
+    ++row;
+#endif
+
+    layout->addWidget(new QLabel(tr("Local:")), row, 1);
+
+    m_openButton = new ClickableLabel;
+    QFont f(m_openButton->font());
+    f.setBold(true);
+    m_openButton->setFont(f);
+    m_openButton->setMouseUnderline(true);
+    connect(m_openButton, SIGNAL(clicked()), this, SLOT(openButtonClicked()));
+    layout->addWidget(m_openButton, row, 2, 1, 2);
+
+    ++row;
+    layout->addWidget(new QLabel(tr("Remote:")), row, 1);
+    m_remoteURLLabel = new SqueezedLabel;
+    m_remoteURLLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
+    layout->addWidget(m_remoteURLLabel, row, 2, 1, 2);
+
+    ++row;
+    layout->addWidget(new QLabel(tr("State:")), row, 1);
+    m_stateLabel = new QLabel;
+    layout->addWidget(m_stateLabel, row, 2, 1, 2);
+
+    layout->setColumnStretch(2, 20);
+
+
+}
+
+WorkStatusWidget::~WorkStatusWidget()
+{
+}
+
+void
+WorkStatusWidget::setLocalPath(QString p)
+{
+    m_localPath = p;
+    m_openButton->setText(p);
+    m_openButton->setEnabled(QDir(m_localPath).exists());
+}
+
+void
+WorkStatusWidget::setRemoteURL(QString r)
+{
+    m_remoteURL = r;
+    m_remoteURLLabel->setText(r);
+}
+
+void
+WorkStatusWidget::setState(QString b)
+{
+    m_state = b;
+    updateStateLabel();
+}
+
+void
+WorkStatusWidget::updateStateLabel()
+{
+    m_stateLabel->setText(m_state);
+}
+
+void
+WorkStatusWidget::openButtonClicked()
+{
+    QDir d(m_localPath);
+    if (d.exists()) {
+        QStringList args;
+        QString path = d.canonicalPath();
+#if defined Q_OS_WIN32
+        // Although the Win32 API is quite happy to have
+        // forward slashes as directory separators, Windows
+        // Explorer is not
+        path = path.replace('/', '\\');
+        args << path;
+        QProcess::execute("c:/windows/explorer.exe", args);
+#else
+        args << path;
+        QProcess::execute(
+#if defined Q_OS_MAC
+            "/usr/bin/open",
+#else
+            "/usr/bin/xdg-open",
+#endif
+            args);
+#endif
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/workstatuswidget.h	Thu Dec 06 13:54:34 2018 +0000
@@ -0,0 +1,64 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    EasyMercurial
+
+    Based on HgExplorer by Jari Korhonen
+    Copyright (c) 2010 Jari Korhonen
+    Copyright (c) 2013 Chris Cannam
+    Copyright (c) 2013 Queen Mary, University of London
+    
+    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 WORKSTATUSWIDGET_H
+#define WORKSTATUSWIDGET_H
+
+#include <QWidget>
+
+class SqueezedLabel;
+
+class QLabel;
+class QPushButton;
+class QFileInfo;
+class ClickableLabel;
+class QCheckBox;
+
+class WorkStatusWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    WorkStatusWidget(QWidget *parent = 0);
+    ~WorkStatusWidget();
+
+    QString localPath() const { return m_localPath; }
+    void setLocalPath(QString p);
+
+    QString remoteURL() const { return m_remoteURL; }
+    void setRemoteURL(QString u);
+
+    QString state() const { return m_state; }
+    void setState(QString b);
+
+private slots:
+    void openButtonClicked();
+
+private:
+    QString m_localPath;
+    ClickableLabel *m_openButton;
+
+    QString m_remoteURL;
+    SqueezedLabel *m_remoteURLLabel;
+
+    QString m_state;
+    QLabel *m_stateLabel;
+
+    void updateStateLabel();
+};
+
+#endif
--- a/startupdialog.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "startupdialog.h"
-#include "common.h"
-
-#include <QGridLayout>
-#include <QDialogButtonBox>
-#include <QSettings>
-
-StartupDialog::StartupDialog(QWidget *parent) :
-    QDialog(parent)
-{
-    setModal(true);
-    setWindowTitle(tr("About me"));
-
-    QSettings settings;
-    settings.beginGroup("User Information");
-    m_name = settings.value("name", getUserRealName()).toString();
-    m_email = settings.value("email").toString();
-    
-    QGridLayout *layout = new QGridLayout;
-    int row = 0;
-
-    layout->addWidget(new QLabel(tr("<qt><big>Welcome to EasyMercurial!</qt></big><br>How would you like to be identified in commit messages?")),
-		      row++, 0, 1, 2);
-
-    layout->addWidget(new QLabel(tr("Name:")), row, 0);
-
-    m_nameEdit = new QLineEdit();
-    m_nameEdit->setText(m_name);
-    connect(m_nameEdit, SIGNAL(textChanged(const QString &)),
-	    this, SLOT(realNameChanged(const QString &)));
-    layout->addWidget(m_nameEdit, row++, 1);
-    
-    layout->addWidget(new QLabel(tr("Email address:")), row, 0);
-
-    m_emailEdit = new QLineEdit();
-    m_emailEdit->setText(m_email);
-    connect(m_emailEdit, SIGNAL(textChanged(const QString &)),
-	    this, SLOT(emailChanged(const QString &)));
-    layout->addWidget(m_emailEdit, row++, 1);
-
-    layout->addWidget(new QLabel(tr("<br>You will appear as:")), row, 0, Qt::AlignBottom);
-    m_example = new QLabel();
-    layout->addWidget(m_example, row++, 1, Qt::AlignBottom);
-
-    QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok);
-    connect(bbox, SIGNAL(accepted()), this, SLOT(accept()));
-    layout->addWidget(bbox, row++, 0, 1, 2);
-    m_ok = bbox->button(QDialogButtonBox::Ok);
-    m_ok->setEnabled(false);
-    
-    setLayout(layout);
-
-    m_ok->setEnabled(m_name != "");
-    updateExample();
-}
-
-void
-StartupDialog::realNameChanged(const QString &s)
-{
-    m_name = s.trimmed();
-    m_ok->setEnabled(m_name != "");
-    updateExample();
-}
-
-void
-StartupDialog::emailChanged(const QString &s)
-{
-    m_email = s.trimmed();
-    updateExample();
-}
-
-void
-StartupDialog::accept()
-{
-    QSettings settings;
-    settings.beginGroup("User Information");
-    settings.setValue("name", m_name);
-    settings.setValue("email", m_email);
-    QDialog::accept();
-}
-
-void
-StartupDialog::updateExample()
-{
-    QString identifier;
-
-    if (m_email != "") {
-	identifier = QString("%1 <%2>").arg(m_name).arg(m_email);
-    } else {
-	identifier = m_name;
-    }
-
-    m_example->setText(identifier);
-}
-
-    
--- a/startupdialog.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 STARTUP_DIALOG_H
-#define STARTUP_DIALOG_H
-
-#include <QDialog>
-#include <QLineEdit>
-#include <QLabel>
-#include <QPushButton>
-
-class StartupDialog : public QDialog
-{
-    Q_OBJECT
-
-public:
-    StartupDialog(QWidget *parent = 0);
-    
-private slots:
-    void realNameChanged(const QString &);
-    void emailChanged(const QString &);
-    void accept();
-
-private:
-    QLineEdit *m_nameEdit;
-    QLineEdit *m_emailEdit;
-    QLabel *m_example;
-    QPushButton *m_ok;
-
-    QString m_name;
-    QString m_email;
-
-    void updateExample();
-};
-
-#endif
--- a/textabbrev.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,314 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "textabbrev.h"
-
-#include <QFontMetrics>
-#include <QApplication>
-
-#include <iostream>
-
-QString
-TextAbbrev::getDefaultEllipsis()
-{
-    return "...";
-}
-
-int
-TextAbbrev::getFuzzLength(QString ellipsis)
-{
-    int len = ellipsis.length();
-    if (len < 3) return len + 3;
-    else if (len > 5) return len + 5;
-    else return len * 2;
-}
-
-int
-TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis)
-{
-    int width = metrics.width(ellipsis);
-    return width * 2;
-}
-
-QString
-TextAbbrev::abbreviateTo(QString text, int characters, Policy policy,
-                         QString ellipsis)
-{
-    switch (policy) {
-
-    case ElideEnd:
-    case ElideEndAndCommonPrefixes:
-        text = text.left(characters) + ellipsis;
-        break;
-        
-    case ElideStart:
-        text = ellipsis + text.right(characters);
-        break;
-
-    case ElideMiddle:
-        if (characters > 2) {
-            text = text.left(characters/2 + 1) + ellipsis
-                + text.right(characters - (characters/2 + 1));
-        } else {
-            text = text.left(characters) + ellipsis;
-        }
-        break;
-    }
-
-    return text;
-}
-
-QString
-TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy,
-                       QString ellipsis)
-{
-    if (ellipsis == "") ellipsis = getDefaultEllipsis();
-    int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
-    if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
-    if (text.length() <= maxLength + fl) return text;
-
-    int truncated = maxLength - ellipsis.length();
-    return abbreviateTo(text, truncated, policy, ellipsis);
-}
-
-QString
-TextAbbrev::abbreviate(QString text,
-                       const QFontMetrics &metrics, int &maxWidth,
-                       Policy policy, QString ellipsis, int wrapLines)
-{
-    if (ellipsis == "") ellipsis = getDefaultEllipsis();
-
-    int tw = metrics.width(text);
-
-    if (tw <= maxWidth) {
-        maxWidth = tw;
-        return text;
-    }
-
-    int truncated = text.length();
-    QString original = text;
-
-    if (wrapLines < 2) {
-
-        while (tw > maxWidth && truncated > 1) {
-
-            truncated--;
-            
-            if (truncated > ellipsis.length()) {
-                text = abbreviateTo(original, truncated, policy, ellipsis);
-            } else {
-                break;
-            }
-            
-            tw = metrics.width(text);
-        }
-        
-        maxWidth = tw;
-        return text;
-
-    } else {
-        
-        QStringList words = text.split(' ', QString::SkipEmptyParts);
-        text = "";
-
-        tw = 0;
-        int i = 0;
-        QString good = "";
-        int lastUsed = 0;
-        while (tw < maxWidth && i < words.size()) {
-            if (text != "") text += " ";
-            text += words[i++];
-            tw = metrics.width(text);
-            if (tw < maxWidth) {
-                good = text;
-                lastUsed = i;
-            }
-        }
-            
-        if (tw < maxWidth) {
-            maxWidth = tw;
-            return text;
-        }
-
-        text = good;
-
-        QString remainder;
-        while (lastUsed < words.size()) {
-            if (remainder != "") remainder += " ";
-            remainder += words[lastUsed++];
-        }
-        remainder = abbreviate(remainder, metrics, maxWidth,
-                               policy, ellipsis, wrapLines - 1);
-
-        maxWidth = std::max(maxWidth, tw);
-        text = text + '\n' + remainder;
-        return text;
-    }
-}
-
-QStringList
-TextAbbrev::abbreviate(const QStringList &texts, int maxLength,
-                       Policy policy, bool fuzzy, QString ellipsis)
-{
-    if (policy == ElideEndAndCommonPrefixes &&
-        texts.size() > 1) {
-
-        if (ellipsis == "") ellipsis = getDefaultEllipsis();
-        int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
-        if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
-
-        int maxOrigLength = 0;
-        for (int i = 0; i < texts.size(); ++i) {
-            int len = texts[i].length();
-            if (len > maxOrigLength) maxOrigLength = len;
-        }
-        if (maxOrigLength <= maxLength + fl) return texts;
-
-        return abbreviate(elidePrefixes
-                          (texts, maxOrigLength - maxLength, ellipsis),
-                          maxLength, ElideEnd, fuzzy, ellipsis);
-    }
-
-    QStringList results;
-    for (int i = 0; i < texts.size(); ++i) {
-        results.push_back
-            (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis));
-    }
-    return results;
-}
-
-QStringList
-TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics,
-                       int &maxWidth, Policy policy, QString ellipsis)
-{
-    if (policy == ElideEndAndCommonPrefixes &&
-        texts.size() > 1) {
-
-        if (ellipsis == "") ellipsis = getDefaultEllipsis();
-
-        int maxOrigWidth = 0;
-        for (int i = 0; i < texts.size(); ++i) {
-            int w = metrics.width(texts[i]);
-            if (w > maxOrigWidth) maxOrigWidth = w;
-        }
-
-        return abbreviate(elidePrefixes(texts, metrics,
-                                        maxOrigWidth - maxWidth, ellipsis),
-                          metrics, maxWidth, ElideEnd, ellipsis);
-    }
-
-    QStringList results;
-    int maxAbbrWidth = 0;
-    for (int i = 0; i < texts.size(); ++i) {
-        int width = maxWidth;
-        QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis);
-        if (width > maxAbbrWidth) maxAbbrWidth = width;
-        results.push_back(abbr);
-    }
-    maxWidth = maxAbbrWidth;
-    return results;
-}
-
-QStringList
-TextAbbrev::elidePrefixes(const QStringList &texts,
-                          int targetReduction,
-                          QString ellipsis)
-{
-    if (texts.empty()) return texts;
-    int plen = getPrefixLength(texts);
-    int fl = getFuzzLength(ellipsis);
-    if (plen < fl) return texts;
-
-    QString prefix = texts[0].left(plen);
-    int truncated = plen;
-    if (plen >= targetReduction + fl) {
-        truncated = plen - targetReduction;
-    } else {
-        truncated = fl;
-    }
-    prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis);
-
-    QStringList results;
-    for (int i = 0; i < texts.size(); ++i) {
-        results.push_back
-            (prefix + texts[i].right(texts[i].length() - plen));
-    }
-    return results;
-}
-
-QStringList
-TextAbbrev::elidePrefixes(const QStringList &texts,
-                          const QFontMetrics &metrics,
-                          int targetWidthReduction,
-                          QString ellipsis)
-{
-    if (texts.empty()) return texts;
-    int plen = getPrefixLength(texts);
-    int fl = getFuzzLength(ellipsis);
-    if (plen < fl) return texts;
-
-    QString prefix = texts[0].left(plen);
-    int pwid = metrics.width(prefix);
-    int twid = pwid - targetWidthReduction;
-    if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2;
-    prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis);
-
-    QStringList results;
-    for (int i = 0; i < texts.size(); ++i) {
-        results.push_back
-            (prefix + texts[i].right(texts[i].length() - plen));
-    }
-    return results;
-}
-
-static bool
-havePrefix(QString prefix, const QStringList &texts)
-{
-    for (int i = 1; i < texts.size(); ++i) {
-        if (!texts[i].startsWith(prefix)) return false;
-    }
-    return true;
-}
-
-int
-TextAbbrev::getPrefixLength(const QStringList &texts)
-{
-    QString reference = texts[0];
-
-    if (reference == "" || havePrefix(reference, texts)) {
-        return reference.length();
-    }
-
-    int candidate = reference.length();
-    QString splitChars(";:,./#-!()$_+=[]{}\\");
-
-    while (--candidate > 1) {
-        if (splitChars.contains(reference[candidate])) {
-            if (havePrefix(reference.left(candidate), texts)) {
-                break;
-            }
-        }
-    }
-
-//    std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl;
-//    for (int i = 0; i < texts.size(); ++i) {
-//        std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl;
-//    }
-
-    return candidate;
-}
-
--- a/textabbrev.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 _TEXT_ABBREV_H_
-#define _TEXT_ABBREV_H_
-
-#include <QString>
-#include <QStringList>
-
-class QFontMetrics;
-
-class TextAbbrev 
-{
-public:
-    enum Policy {
-        ElideEnd,
-        ElideEndAndCommonPrefixes,
-        ElideStart,
-        ElideMiddle
-    };
-
-    /**
-     * Abbreviate the given text to the given maximum length
-     * (including ellipsis), using the given abbreviation policy.  If
-     * fuzzy is true, the text will be left alone if it is "not much
-     * more than" the maximum length.
-     * 
-     * If ellipsis is non-empty, it will be used to show elisions in
-     * preference to the default (which is "...").
-     */
-    static QString abbreviate(QString text, int maxLength,
-                              Policy policy = ElideEnd,
-                              bool fuzzy = true,
-                              QString ellipsis = "");
-
-    /**
-     * Abbreviate the given text to the given maximum painted width,
-     * using the given abbreviation policy.  maxWidth is also modified
-     * so as to return the painted width of the abbreviated text.
-     *
-     * If ellipsis is non-empty, it will be used to show elisions in
-     * preference to the default (which is tr("...")).
-     */
-    static QString abbreviate(QString text,
-                              const QFontMetrics &metrics,
-                              int &maxWidth,
-                              Policy policy = ElideEnd,
-                              QString ellipsis = "",
-                              int wrapLines = 1);
-    
-    /**
-     * Abbreviate all of the given texts to the given maximum length,
-     * using the given abbreviation policy.  If fuzzy is true, texts
-     * that are "not much more than" the maximum length will be left
-     * alone.
-     *
-     * If ellipsis is non-empty, it will be used to show elisions in
-     * preference to the default (which is tr("...")).
-     */
-    static QStringList abbreviate(const QStringList &texts, int maxLength,
-                                  Policy policy = ElideEndAndCommonPrefixes,
-                                  bool fuzzy = true,
-                                  QString ellipsis = "");
-
-    /**
-     * Abbreviate all of the given texts to the given maximum painted
-     * width, using the given abbreviation policy.  maxWidth is also
-     * modified so as to return the maximum painted width of the
-     * abbreviated texts.
-     *
-     * If ellipsis is non-empty, it will be used to show elisions in
-     * preference to the default (which is tr("...")).
-     */
-    static QStringList abbreviate(const QStringList &texts,
-                                  const QFontMetrics &metrics,
-                                  int &maxWidth,
-                                  Policy policy = ElideEndAndCommonPrefixes,
-                                  QString ellipsis = "");
-
-protected:
-    static QString getDefaultEllipsis();
-    static int getFuzzLength(QString ellipsis);
-    static int getFuzzWidth(const QFontMetrics &metrics, QString ellipsis);
-    static QString abbreviateTo(QString text, int characters,
-                                Policy policy, QString ellipsis);
-    static QStringList elidePrefixes(const QStringList &texts,
-                                     int targetReduction,
-                                     QString ellipsis);
-    static QStringList elidePrefixes(const QStringList &texts,
-                                     const QFontMetrics &metrics,
-                                     int targetWidthReduction,
-                                     QString ellipsis);
-    static int getPrefixLength(const QStringList &texts);
-};
-
-#endif
-
--- a/uncommitteditem.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "uncommitteditem.h"
-#include "colourset.h"
-#include "debug.h"
-#include "textabbrev.h"
-
-#include <QPainter>
-#include <QGraphicsScene>
-#include <QGraphicsSceneMouseEvent>
-#include <QMenu>
-#include <QAction>
-#include <QLabel>
-#include <QWidgetAction>
-
-UncommittedItem::UncommittedItem() :
-    m_showBranch(false), m_isNewBranch(false),
-    m_column(0), m_row(0), m_wide(false)
-{
-    m_font = QFont();
-    m_font.setPixelSize(11);
-    m_font.setBold(false);
-    m_font.setItalic(false);
-    setCursor(Qt::ArrowCursor);
-}
-
-QRectF
-UncommittedItem::boundingRect() const
-{
-    //!!! this stuff is gross, refactor with changesetitem and connectionitem
-    int w = 100;
-    if (m_wide) w = 180;
-    return QRectF(-((w-50)/2 - 1), -30, w - 3, 79 + 40);
-}
-
-void
-UncommittedItem::mousePressEvent(QGraphicsSceneMouseEvent *e)
-{
-    DEBUG << "UncommittedItem::mousePressEvent" << endl;
-    if (e->button() == Qt::RightButton) {
-        activateMenu();
-    }
-}
-
-void
-UncommittedItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *e)
-{
-    DEBUG << "UncommittedItem::mouseDoubleClickEvent" << endl;
-    if (e->button() == Qt::LeftButton) {
-        emit showWork();
-    }
-}
-
-void
-UncommittedItem::activateMenu()
-{
-    QMenu *menu = new QMenu;
-    QLabel *label = new QLabel(tr("<qt><b>&nbsp;Uncommitted changes</b></qt>"));
-    QWidgetAction *wa = new QWidgetAction(menu);
-    wa->setDefaultWidget(label);
-    menu->addAction(wa);
-    menu->addSeparator();
-
-    QAction *dif = menu->addAction(tr("Diff"));
-    connect(dif, SIGNAL(triggered()), this, SIGNAL(diff()));
-    QAction *stat = menu->addAction(tr("Summarise changes"));
-    connect(stat, SIGNAL(triggered()), this, SIGNAL(showSummary()));
-    
-    menu->addSeparator();
-
-    QAction *commit = menu->addAction(tr("Commit..."));
-    connect(commit, SIGNAL(triggered()), this, SIGNAL(commit()));
-    QAction *revert = menu->addAction(tr("Revert..."));
-    connect(revert, SIGNAL(triggered()), this, SIGNAL(revert()));
-
-    menu->addSeparator();
-
-    QAction *branch = menu->addAction(tr("Start new branch..."));
-    connect(branch, SIGNAL(triggered()), this, SIGNAL(newBranch()));
-    QAction *nobranch = menu->addAction(tr("Cancel new branch"));
-    nobranch->setEnabled(m_isNewBranch);
-    connect(nobranch, SIGNAL(triggered()), this, SIGNAL(noBranch()));
-
-    menu->exec(QCursor::pos());
-
-    ungrabMouse();
-}
-
-void
-UncommittedItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *option,
-		       QWidget *w)
-{
-    paint->save();
-    
-    ColourSet *colourSet = ColourSet::instance();
-    QColor branchColour = colourSet->getColourFor(m_branch);
-
-    QFont f(m_font);
-
-    QTransform t = paint->worldTransform();
-    float scale = std::min(t.m11(), t.m22());
-    if (scale > 1.0) {
-	int ps = int((f.pixelSize() / scale) + 0.5);
-	if (ps < 8) ps = 8;
-	f.setPixelSize(ps);
-    }
-
-    if (scale < 0.1) {
-	paint->setPen(QPen(branchColour, 0, Qt::DashLine));
-    } else {
-	paint->setPen(QPen(branchColour, 2, Qt::DashLine));
-    }
-	
-    paint->setFont(f);
-    QFontMetrics fm(f);
-    int fh = fm.height();
-
-    int width = 100;
-    if (m_wide) width = 180;
-    int x0 = -((width - 50) / 2 - 1);
-
-    int height = 49;
-    QRectF r(x0, 0, width - 3, height);
-    paint->setBrush(Qt::white);
-    paint->drawRect(r);
-
-    if (m_wide) {
-        QString label = tr("Uncommitted changes");
-        paint->drawText(-(fm.width(label) - 50)/2,
-                        25 - fm.height()/2 + fm.ascent(),
-                        label);
-    } else {
-        QString label = tr("Uncommitted");
-        paint->drawText(-(fm.width(label) - 50)/2,
-                        25 - fm.height() + fm.ascent(),
-                        label);
-        label = tr("changes");
-        paint->drawText(-(fm.width(label) - 50)/2,
-                        25 + fm.ascent(),
-                        label);
-    }        
-
-    if (m_showBranch && m_branch != "") {
-        // write branch name
-        f.setBold(true);
-        paint->setFont(f);
-        int wid = width - 3;
-        QString b = TextAbbrev::abbreviate(m_branch, QFontMetrics(f), wid);
-        paint->drawText(x0, -fh + fm.ascent() - 4, b);
-        f.setBold(false);
-    }
-
-    paint->restore();
-    return;
-}
--- a/uncommitteditem.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 UNCOMMITTEDITEM_H
-#define UNCOMMITTEDITEM_H
-
-#include <QGraphicsObject>
-#include <QFont>
-
-class UncommittedItem : public QGraphicsObject
-{
-    Q_OBJECT
-
-public:
-    UncommittedItem();
-
-    virtual QRectF boundingRect() const;
-    virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
-
-    QString branch() const { return m_branch; }
-    void setBranch(QString b) { m_branch = b; }
-
-    bool showBranch() const { return m_showBranch; }
-    void setShowBranch(bool s) { m_showBranch = s; }
-
-    bool isNewBranch() const { return m_isNewBranch; }
-    void setIsNewBranch(bool s) { m_isNewBranch = s; }
-    
-    int column() const { return m_column; }
-    int row() const { return m_row; }
-    void setColumn(int c) { m_column = c; setX(c * 100); }
-    void setRow(int r) { m_row = r; setY(r * 90); }
-
-    bool isWide() const { return m_wide; }
-    void setWide(bool w) { m_wide = w; }
-
-signals:
-    void commit();
-    void revert();
-    void diff();
-    void showSummary();
-    void showWork();
-    void newBranch();
-    void noBranch();
-
-protected:
-    virtual void mousePressEvent(QGraphicsSceneMouseEvent *);
-    virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *);
-
-private:
-    void activateMenu();
-
-    QString m_branch;
-    bool m_showBranch;
-    bool m_isNewBranch;
-    QFont m_font;
-    int m_column;
-    int m_row;
-    bool m_wide;
-};
-
-#endif // UNCOMMITTEDITEM_H
--- a/version.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-#define EASYHG_VERSION "0.9"
--- a/workstatuswidget.cpp	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 "workstatuswidget.h"
-#include "debug.h"
-#include "clickablelabel.h"
-
-#include <QGridLayout>
-#include <QSpacerItem>
-#include <QLabel>
-#include <QProcess>
-#include <QDir>
-
-WorkStatusWidget::WorkStatusWidget(QWidget *parent) :
-    QWidget(parent)
-{
-    QGridLayout *layout = new QGridLayout;
-    layout->setMargin(6);
-    layout->setSpacing(6);
-    setLayout(layout);
-
-    int row = 0;
-
-#ifndef Q_OS_MAC    
-    layout->addItem(new QSpacerItem(1, 1), row, 0);
-    ++row;
-#endif
-
-    layout->addWidget(new QLabel(tr("Local:")), row, 1);
-
-    m_openButton = new ClickableLabel;
-    QFont f(m_openButton->font());
-    f.setBold(true);
-    m_openButton->setFont(f);
-    m_openButton->setMouseUnderline(true);
-    connect(m_openButton, SIGNAL(clicked()), this, SLOT(openButtonClicked()));
-    layout->addWidget(m_openButton, row, 2, 1, 2, Qt::AlignLeft);
-
-    ++row;
-    layout->addWidget(new QLabel(tr("Remote:")), row, 1);
-    m_remoteURLLabel = new QLabel;
-    layout->addWidget(m_remoteURLLabel, row, 2, 1, 2);
-
-    ++row;
-    layout->addWidget(new QLabel(tr("State:")), row, 1);
-    m_stateLabel = new QLabel;
-    layout->addWidget(m_stateLabel, row, 2, 1, 2);
-
-    layout->setColumnStretch(2, 20);
-
-
-}
-
-WorkStatusWidget::~WorkStatusWidget()
-{
-}
-
-void
-WorkStatusWidget::setLocalPath(QString p)
-{
-    m_localPath = p;
-    m_openButton->setText(p);
-    m_openButton->setEnabled(QDir(m_localPath).exists());
-}
-
-void
-WorkStatusWidget::setRemoteURL(QString r)
-{
-    m_remoteURL = r;
-    m_remoteURLLabel->setText(r);
-}
-
-void
-WorkStatusWidget::setState(QString b)
-{
-    m_state = b;
-    updateStateLabel();
-}
-
-void
-WorkStatusWidget::updateStateLabel()
-{
-    m_stateLabel->setText(m_state);
-}
-
-void
-WorkStatusWidget::openButtonClicked()
-{
-    QDir d(m_localPath);
-    if (d.exists()) {
-        QStringList args;
-        QString path = d.canonicalPath();
-#if defined Q_OS_WIN32
-        // Although the Win32 API is quite happy to have
-        // forward slashes as directory separators, Windows
-        // Explorer is not
-        path = path.replace('/', '\\');
-        args << path;
-        QProcess::execute("c:/windows/explorer.exe", args);
-#else
-        args << path;
-        QProcess::execute(
-#if defined Q_OS_MAC
-            "/usr/bin/open",
-#else
-            "/usr/bin/xdg-open",
-#endif
-            args);
-#endif
-    }
-}
--- a/workstatuswidget.h	Tue Mar 15 12:36:26 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    EasyMercurial
-
-    Based on HgExplorer by Jari Korhonen
-    Copyright (c) 2010 Jari Korhonen
-    Copyright (c) 2011 Chris Cannam
-    Copyright (c) 2011 Queen Mary, University of London
-    
-    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 WORKSTATUSWIDGET_H
-#define WORKSTATUSWIDGET_H
-
-#include <QWidget>
-
-class QLabel;
-class QPushButton;
-class QFileInfo;
-class ClickableLabel;
-class QCheckBox;
-
-class WorkStatusWidget : public QWidget
-{
-    Q_OBJECT
-
-public:
-    WorkStatusWidget(QWidget *parent = 0);
-    ~WorkStatusWidget();
-
-    QString localPath() const { return m_localPath; }
-    void setLocalPath(QString p);
-
-    QString remoteURL() const { return m_remoteURL; }
-    void setRemoteURL(QString u);
-
-    QString state() const { return m_state; }
-    void setState(QString b);
-
-private slots:
-    void openButtonClicked();
-
-private:
-    QString m_localPath;
-    ClickableLabel *m_openButton;
-
-    QString m_remoteURL;
-    QLabel *m_remoteURLLabel;
-
-    QString m_state;
-    QLabel *m_stateLabel;
-
-    void updateStateLabel();
-};
-
-#endif