changeset 109:1721c580c10e

* Add a queueing mechanism for Hg actions, instead of refusing to start an action if something else is already happening. This is essential now that actions can be prompted by asynchronous events (e.g. filesystem watcher). * Make Revert behave sensibly
author Chris Cannam
date Fri, 26 Nov 2010 12:48:29 +0000
parents 8ae3b44c0073
children 0f039d3cc38e
files common.h confirmcommentdialog.cpp confirmcommentdialog.h easyhg.pro filestates.cpp filestatuswidget.cpp filestatuswidget.h hgaction.h hgrunner.cpp hgrunner.h hgtabwidget.cpp hgtabwidget.h mainwindow.cpp mainwindow.h
diffstat 14 files changed, 755 insertions(+), 770 deletions(-) [+]
line wrap: on
line diff
--- a/common.h	Thu Nov 25 21:08:17 2010 +0000
+++ b/common.h	Fri Nov 26 12:48:29 2010 +0000
@@ -21,25 +21,9 @@
 #include <QString>
 
 #define MY_ICON_SIZE                    32
+//!!!:
 #define REPOMENU_TITLE                  "Repository actions"
 #define WORKFOLDERMENU_TITLE            "Workfolder actions"
-#define EXITOK(x)                       ((x)==0)
-#define CHGSET                          "changeset: "
-#define REQUIRED_CHGSET_DIFF_COUNT      2
-
-#define WORKTAB                         0
-#define HISTORYTAB                      2
-#define HEADSTAB                        3
-
-#define HGSTAT_M_BIT    1U
-#define HGSTAT_A_BIT    2U
-#define HGSTAT_R_BIT    4U
-#define HGSTAT_D_BIT    8U
-#define HGSTAT_U_BIT    16U
-#define HGSTAT_C_BIT    32U
-#define HGSTAT_I_BIT    64U
-
-#define DEFAULT_HG_STAT_BITS (HGSTAT_M_BIT | HGSTAT_A_BIT | HGSTAT_R_BIT | HGSTAT_D_BIT | HGSTAT_U_BIT)
 
 extern QString findExecutable(QString name);
 
--- a/confirmcommentdialog.cpp	Thu Nov 25 21:08:17 2010 +0000
+++ b/confirmcommentdialog.cpp	Fri Nov 26 12:48:29 2010 +0000
@@ -16,6 +16,7 @@
 */
 
 #include "confirmcommentdialog.h"
+#include "common.h"
 
 #include <QMessageBox>
 #include <QInputDialog>
@@ -64,6 +65,18 @@
     return m_textEdit->document()->toPlainText();
 }
 
+QString ConfirmCommentDialog::buildFilesText(QString intro, QStringList files)
+{
+    QString text;
+    text = "<qt>" + intro;
+    text += "<p><code>";
+    foreach (QString file, files) {
+        text += "&nbsp;&nbsp;&nbsp;" + xmlEncode(file) + "<br>";
+    }
+    text += "</code></qt>";
+    return text;
+}
+
 bool ConfirmCommentDialog::confirmFilesAction(QWidget *parent,
                                               QString title,
                                               QString introText,
@@ -72,14 +85,9 @@
 {
     QString text;
     if (files.size() <= 10) {
-        text = "<qt>" + introText;
-        text += "<code>";
-        foreach (QString file, files) {
-            text += file + "<br>";
-        }
-        text += "</code></qt>";
+        text = buildFilesText(introText, files);
     } else {
-        text = "<qt>" + introText.arg(files.size());
+        text = "<qt>" + introTextWithCount.arg(files.size()) + "</qt>";
     }
     return (QMessageBox::information(parent,
                                      title,
@@ -89,6 +97,48 @@
             == QMessageBox::Ok);
 }
 
+bool ConfirmCommentDialog::confirmDangerousFilesAction(QWidget *parent,
+                                                       QString title,
+                                                       QString introText,
+                                                       QString introTextWithCount,
+                                                       QStringList files)
+{
+    QString text;
+    if (files.size() <= 10) {
+        text = buildFilesText(introText, files);
+    } else {
+        text = "<qt>" + introTextWithCount.arg(files.size()) + "</qt>";
+    }
+    return (QMessageBox::warning(parent,
+                                 title,
+                                 text,
+                                 QMessageBox::Ok | QMessageBox::Cancel,
+                                 QMessageBox::Cancel)
+            == QMessageBox::Ok);
+}
+
+bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent,
+                                                     QString title,
+                                                     QString introText,
+                                                     QString introTextWithCount,
+                                                     QStringList files,
+                                                     QString &comment)
+{
+    return confirmAndComment(parent, title, introText,
+                             introTextWithCount, files, comment, false);
+}
+
+bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent,
+                                                    QString title,
+                                                    QString introText,
+                                                    QString introTextWithCount,
+                                                    QStringList files,
+                                                    QString &comment)
+{
+    return confirmAndComment(parent, title, introText,
+                             introTextWithCount, files, comment, true);
+}
+
 bool ConfirmCommentDialog::confirmAndComment(QWidget *parent,
                                              QString title,
                                              QString introText,
@@ -99,18 +149,30 @@
 {
     QString text;
     if (files.size() <= 10) {
-        text = "<qt>" + introText;
-        text += "<p><ul>";
-        foreach (QString file, files) {
-            text += "<li>" + file + "</li>";
-        }
-        text += "</ul><p>Please enter your comment:</qt>";
+        text = buildFilesText(introText, files);
     } else {
-        text = "<qt>" + introText.arg(files.size());
+        text = "<qt>" + introTextWithCount.arg(files.size());
     }
+    text += tr("<p>Please enter your comment:</qt>");
     return confirmAndComment(parent, title, text, comment, longComment);
 }
 
+bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent,
+                                                     QString title,
+                                                     QString introText,
+                                                     QString &comment)
+{
+    return confirmAndComment(parent, title, introText, comment, false);
+}
+
+bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent,
+                                                    QString title,
+                                                    QString introText,
+                                                    QString &comment)
+{
+    return confirmAndComment(parent, title, introText, comment, true);
+}
+
 bool ConfirmCommentDialog::confirmAndComment(QWidget *parent,
                                              QString title,
                                              QString introText,
--- a/confirmcommentdialog.h	Thu Nov 25 21:08:17 2010 +0000
+++ b/confirmcommentdialog.h	Fri Nov 26 12:48:29 2010 +0000
@@ -36,6 +36,45 @@
                                    QString introTextWithCount,
                                    QStringList files);
 
+    static bool confirmDangerousFilesAction(QWidget *parent,
+                                            QString title,
+                                            QString introText,
+                                            QString introTextWithCount,
+                                            QStringList files);
+
+    static bool confirmAndGetShortComment(QWidget *parent,
+                                          QString title,
+                                          QString introText,
+                                          QString introTextWithCount,
+                                          QStringList files,
+                                          QString &comment);
+
+    static bool confirmAndGetLongComment(QWidget *parent,
+                                         QString title,
+                                         QString introText,
+                                         QString introTextWithCount,
+                                         QStringList files,
+                                         QString &comment);
+
+    static bool confirmAndGetShortComment(QWidget *parent,
+                                          QString title,
+                                          QString introText,
+                                          QString &comment);
+
+    static bool confirmAndGetLongComment(QWidget *parent,
+                                         QString title,
+                                         QString introText,
+                                         QString &comment);
+
+private slots:
+    void commentChanged();
+
+private:
+    ConfirmCommentDialog(QWidget *parent,
+                         QString title,
+                         QString introText,
+                         QString initialComment);
+
     static bool confirmAndComment(QWidget *parent,
                                   QString title,
                                   QString introText,
@@ -50,14 +89,7 @@
                                   QString &comment,
                                   bool longComment);
 
-private slots:
-    void commentChanged();
-
-private:
-    ConfirmCommentDialog(QWidget *parent,
-                         QString title,
-                         QString introText,
-                         QString initialComment);
+    static QString buildFilesText(QString intro, QStringList files);
 
     QString getComment() const;
 
--- a/easyhg.pro	Thu Nov 25 21:08:17 2010 +0000
+++ b/easyhg.pro	Fri Nov 26 12:48:29 2010 +0000
@@ -33,7 +33,8 @@
     selectablelabel.h \
     filestates.h \
     filestatuswidget.h \
-    confirmcommentdialog.h
+    confirmcommentdialog.h \
+    hgaction.h
 SOURCES = main.cpp \
     mainwindow.cpp \
     hgtabwidget.cpp \
--- a/filestates.cpp	Thu Nov 25 21:08:17 2010 +0000
+++ b/filestates.cpp	Fri Nov 26 12:48:29 2010 +0000
@@ -1,3 +1,20 @@
+/* -*- 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) 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.
+*/
+
 #include "filestates.h"
 
 #include "debug.h"
--- a/filestatuswidget.cpp	Thu Nov 25 21:08:17 2010 +0000
+++ b/filestatuswidget.cpp	Fri Nov 26 12:48:29 2010 +0000
@@ -68,8 +68,9 @@
     m_descriptions[FileStates::Added] = tr("These files will be added to version control next time you commit.");
     m_descriptions[FileStates::Removed] = tr("These files will be removed from version control next time you commit.<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 deleted them intentionally, select them here and use <b>Remove</b> to tell the version control system about it.");
+    m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.<br>"
+                                             "If you deleted them by accident, select them here and use Revert to restore their previous contents.<br>"
+                                             "If you deleted them intentionally, select them here and use Remove to tell the version control system about it.");
     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.");
 
@@ -191,6 +192,33 @@
     return files;
 }
 
+QStringList FileStatusWidget::getSelectedRevertableFiles() const
+{
+    QStringList files;
+    foreach (QString f, m_selectedFiles) {
+        switch (m_fileStates.getStateOfFile(f)) {
+        case FileStates::Added:
+        case FileStates::Modified:
+        case FileStates::Removed:
+        case FileStates::Missing:
+            files.push_back(f);
+            break;
+        default: break;
+        }
+    }
+    return files;
+}
+
+QStringList FileStatusWidget::getAllRevertableFiles() const
+{
+    QStringList files;
+    files << m_fileStates.getFilesInState(FileStates::Modified);
+    files << m_fileStates.getFilesInState(FileStates::Added);
+    files << m_fileStates.getFilesInState(FileStates::Removed);
+    files << m_fileStates.getFilesInState(FileStates::Missing);
+    return files;
+}
+
 QStringList FileStatusWidget::getSelectedAddableFiles() const
 {
     QStringList files;
--- a/filestatuswidget.h	Thu Nov 25 21:08:17 2010 +0000
+++ b/filestatuswidget.h	Fri Nov 26 12:48:29 2010 +0000
@@ -54,6 +54,9 @@
     QStringList getSelectedCommittableFiles() const;
     QStringList getAllCommittableFiles() const;
 
+    QStringList getSelectedRevertableFiles() const;
+    QStringList getAllRevertableFiles() const;
+
     QStringList getSelectedAddableFiles() const;
     QStringList getAllAddableFiles() const;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgaction.h	Fri Nov 26 12:48:29 2010 +0000
@@ -0,0 +1,101 @@
+/* -*- 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) 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.
+*/
+
+#ifndef HGACTION_H
+#define HGACTION_H
+
+#include <QString>
+#include <QStringList>
+
+enum HGACTIONS
+{
+    ACT_NONE,
+    ACT_QUERY_PATHS,
+    ACT_QUERY_BRANCH,
+    ACT_STAT,
+    ACT_QUERY_HEADS,
+    ACT_QUERY_PARENTS,
+    ACT_LOG,
+    ACT_REMOVE,
+    ACT_ADD,
+    ACT_INCOMING,
+    ACT_PUSH,
+    ACT_PULL,
+    ACT_CLONEFROMREMOTE,
+    ACT_INIT,
+    ACT_COMMIT,
+    ACT_ANNOTATE,
+    ACT_FILEDIFF,
+    ACT_FOLDERDIFF,
+    ACT_CHGSETDIFF,
+    ACT_UPDATE,
+    ACT_REVERT,
+    ACT_MERGE,
+    ACT_RESOLVE_LIST,
+    ACT_SERVE,
+    ACT_RESOLVE_MARK,
+    ACT_RETRY_MERGE,
+    ACT_TAG,
+    ACT_HG_IGNORE,
+};
+
+struct HgAction
+{
+    HGACTIONS action;
+    QString workingDir;
+    QStringList params;
+
+    QString executable; // empty for normal Hg
+
+    HgAction() : action(ACT_NONE) { }
+
+    HgAction(HGACTIONS _action, QString _wd, QStringList _params) :
+        action(_action), workingDir(_wd), params(_params) { }
+/*
+    HgAction(const HgAction &a) :
+        action(a.action), workingDir(a.workingDir),
+        params(a.params), executable(a.executable) { }
+
+    HgAction &operator=(const HgAction &a) {
+        if (&a != this) {
+            action = a.action;
+            workingDir = a.workingDir;
+            params = a.params;
+            executable = a.executable;
+        }
+        return *this;
+    }
+*/
+    bool operator==(const HgAction &a) {
+        return (a.action == action && a.workingDir == workingDir &&
+                a.params == params && a.executable == executable);
+    }
+
+    bool mayBeInteractive() const {
+	switch (action) {
+	case ACT_INCOMING:
+	case ACT_PUSH:
+	case ACT_PULL:
+	case ACT_CLONEFROMREMOTE:
+	    return true;
+	default:
+	    return false;
+	}
+    }
+};
+
+#endif
--- a/hgrunner.cpp	Thu Nov 25 21:08:17 2010 +0000
+++ b/hgrunner.cpp	Fri Nov 26 12:48:29 2010 +0000
@@ -72,6 +72,29 @@
     delete m_proc;
 }
 
+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;
@@ -95,17 +118,6 @@
     */
 }
 
-void HgRunner::setProcExitInfo(int procExitCode, QProcess::ExitStatus procExitStatus)
-{
-    m_exitCode = procExitCode;
-    m_exitStatus = procExitStatus;
-}
-
-QString HgRunner::getLastCommandLine()
-{
-    return QString("Command line: " + m_lastHgCommand + " " + m_lastParams);
-}
-
 void HgRunner::noteUsername(QString name)
 {
     m_userName = name;
@@ -200,63 +212,96 @@
 
 void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
 {
-    setProcExitInfo(procExitCode, 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();
 
+    if (completedAction.action == ACT_NONE) {
+        DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl;
+    }
+
     if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
         DEBUG << "HgRunner::finished: Command completed successfully" << endl;
-        emit commandCompleted();
+        //!!! NB this is all output not stdout as it should be
+        emit commandCompleted(completedAction, m_output);
     } else {
         DEBUG << "HgRunner::finished: Command failed" << endl;
-        emit commandFailed();
+        //!!! NB this is all output not stderr as it should be
+        emit commandFailed(completedAction, m_output);
     }
+
+    checkQueue();
 }
-
+/*
 bool HgRunner::isCommandRunning()
 {
     return m_isRunning;
 }
+*/
 
 void HgRunner::killCurrentCommand()
 {
-    if (isCommandRunning()) {
+    if (m_isRunning) {
         m_proc -> kill();
     }
 }
 
-void HgRunner::startHgCommand(QString workingDir, QStringList params, bool interactive)
+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 (executable == "") {
+        // This is a Hg command
+        executable = getHgBinaryName();
 #ifdef Q_OS_WIN32
-    // This at least means we won't block on the non-working password prompt
-    params.push_front("ui.interactive=false");
+        // This at least means we won't block on the non-working password prompt
+        params.push_front("--noninteractive");
 #else
-    // password prompt should work here
-    if (interactive) {
-        params.push_front("ui.interactive=true");
-    } else {
-        params.push_front("ui.interactive=false");
+        // password prompt should work here
+        if (action.mayBeInteractive()) {
+            params.push_front("ui.interactive=true");
+            params.push_front("--config");
+            interactive = true;
+        } else {
+            params.push_front("--noninteractive");
+        }
     }
 #endif
-    params.push_front("--config");
-    startCommand(getHgBinaryName(), workingDir, params, interactive);
-}
 
-void HgRunner::startCommand(QString command, QString workingDir, QStringList params,
-                            bool interactive)
-{
     m_isRunning = true;
     setRange(0, 0);
-    setVisible(true);
+    show();
     m_output.clear();
-    m_exitCode = 0;
-    m_exitStatus = QProcess::NormalExit;
     m_realm = "";
     m_userName = "";
 
-    if (!workingDir.isEmpty()) {
-        m_proc->setWorkingDirectory(workingDir);
+    if (!action.workingDir.isEmpty()) {
+        m_proc->setWorkingDirectory(action.workingDir);
     }
 
     m_procInput = 0;
@@ -279,15 +324,16 @@
     }
 #endif
 
-    m_lastHgCommand = command;
-    m_lastParams = params.join(" ");
-
-    QString cmdline = command;
+    QString cmdline = executable;
     foreach (QString param, params) cmdline += " " + param;
     DEBUG << "HgRunner: starting: " << cmdline << " with cwd "
-          << workingDir << endl;
+          << action.workingDir << endl;
 
-    m_proc->start(command, params);
+    m_currentAction = action;
+
+    DEBUG << "set current action to " << m_currentAction.action << endl;
+    
+    m_proc->start(executable, params);
 }
 
 void HgRunner::closeProcInput()
@@ -303,19 +349,3 @@
 #endif
 }
 
-int HgRunner::getExitCode()
-{
-    return m_exitCode;
-}
-
-QString HgRunner::getOutput()
-{
-    return m_output;
-}
-
-void HgRunner::hideProgBar()
-{
-    setVisible(false);
-}
-
-
--- a/hgrunner.h	Thu Nov 25 21:08:17 2010 +0000
+++ b/hgrunner.h	Fri Nov 26 12:48:29 2010 +0000
@@ -18,12 +18,16 @@
 #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
@@ -32,29 +36,28 @@
     HgRunner(QWidget * parent = 0);
     ~HgRunner();
 
-    void startHgCommand(QString workingDir, QStringList params, bool interactive = false);
-    void startCommand(QString command, QString workingDir, QStringList params, bool interactive = false);
-
+    void requestAction(HgAction action);
+/*
     bool isCommandRunning();
     void killCurrentCommand();
 
-    int getExitCode();
-    QProcess::ExitStatus getExitStatus();
+    void hideProgBar();
+*/    
+signals:
+    void commandCompleted(HgAction action, QString stdout);
+    void commandFailed(HgAction action, QString stderr);
 
-    void hideProgBar();
-
-    QString getOutput();
-    
-signals:
-    void commandCompleted();
-    void commandFailed();
+private slots:
+    void started();
+    void finished(int procExitCode, QProcess::ExitStatus procExitStatus);
+    void dataReady();
 
 private:
-    void setProcExitInfo(int procExitCode, QProcess::ExitStatus procExitStatus);
-    QString getLastCommandLine();
-    void presentErrorToUser();
+    void checkQueue();
+    void startCommand(HgAction action);
     QString getHgBinaryName();
     void closeProcInput();
+    void killCurrentCommand();
 
     void noteUsername(QString);
     void noteRealm(QString);
@@ -70,18 +73,13 @@
     bool m_isRunning;
     QProcess *m_proc;
     QString m_output;
-    int m_exitCode;
-    QProcess::ExitStatus m_exitStatus;
-    QString m_lastHgCommand;
-    QString m_lastParams;
 
     QString m_userName;
     QString m_realm;
 
-private slots:
-    void started();
-    void finished(int procExitCode, QProcess::ExitStatus procExitStatus);
-    void dataReady();
+    typedef std::deque<HgAction> ActionQueue;
+    ActionQueue m_queue;
+    HgAction m_currentAction;
 };
 
 #endif // HGRUNNER_H
--- a/hgtabwidget.cpp	Thu Nov 25 21:08:17 2010 +0000
+++ b/hgtabwidget.cpp	Fri Nov 26 12:48:29 2010 +0000
@@ -66,9 +66,16 @@
 
 bool HgTabWidget::canCommit() const
 {
+    if (!fileStatusWidget->getSelectedAddableFiles().empty()) return false;
     return fileStatusWidget->haveChangesToCommit();
 }
 
+bool HgTabWidget::canRevert() const
+{
+    return fileStatusWidget->haveChangesToCommit() ||
+        !fileStatusWidget->getSelectedRevertableFiles().empty();
+}
+
 bool HgTabWidget::canAdd() const
 {
     if (fileStatusWidget->getSelectedAddableFiles().empty()) return false;
@@ -94,36 +101,41 @@
     return fileStatusWidget->getAllSelectedFiles();
 }
 
+QStringList HgTabWidget::getAllCommittableFiles() const
+{
+    return fileStatusWidget->getAllCommittableFiles();
+}
+
 QStringList HgTabWidget::getSelectedCommittableFiles() const
 {
     return fileStatusWidget->getSelectedCommittableFiles();
 }
 
+QStringList HgTabWidget::getAllRevertableFiles() const
+{
+    return fileStatusWidget->getAllRevertableFiles();
+}
+
+QStringList HgTabWidget::getSelectedRevertableFiles() const
+{
+    return fileStatusWidget->getSelectedRevertableFiles();
+}
+
 QStringList HgTabWidget::getSelectedAddableFiles() const
 {
     return fileStatusWidget->getSelectedAddableFiles();
 }
 
+QStringList HgTabWidget::getAllRemovableFiles() const
+{
+    return fileStatusWidget->getAllRemovableFiles();
+}
+
 QStringList HgTabWidget::getSelectedRemovableFiles() const
 {
     return fileStatusWidget->getSelectedRemovableFiles();
 }
 
-QStringList HgTabWidget::getAllCommittableFiles() const
-{
-    return fileStatusWidget->getAllCommittableFiles();
-}
-
-QStringList HgTabWidget::getAllAddableFiles() const
-{
-    return fileStatusWidget->getAllAddableFiles();
-}
-
-QStringList HgTabWidget::getAllRemovableFiles() const
-{
-    return fileStatusWidget->getAllRemovableFiles();
-}
-
 void HgTabWidget::updateWorkFolderFileList(QString fileList)
 {
     fileStates.parseStates(fileList);
--- a/hgtabwidget.h	Thu Nov 25 21:08:17 2010 +0000
+++ b/hgtabwidget.h	Fri Nov 26 12:48:29 2010 +0000
@@ -48,6 +48,7 @@
     FileStates getFileStates() { return fileStates; }
 
     bool canCommit() const;
+    bool canRevert() const;
     bool canAdd() const;
     bool canRemove() const;
     bool canDoDiff() const;
@@ -57,6 +58,9 @@
     QStringList getSelectedCommittableFiles() const;
     QStringList getAllCommittableFiles() const;
 
+    QStringList getSelectedRevertableFiles() const;
+    QStringList getAllRevertableFiles() const;
+
     QStringList getSelectedAddableFiles() const;
     QStringList getAllAddableFiles() const;
 
--- a/mainwindow.cpp	Thu Nov 25 21:08:17 2010 +0000
+++ b/mainwindow.cpp	Fri Nov 26 12:48:29 2010 +0000
@@ -52,11 +52,10 @@
     createStatusBar();
 
     runner = new HgRunner(this);
-    connect(runner, SIGNAL(commandCompleted()),
-            this, SLOT(commandCompleted()));
-    connect(runner, SIGNAL(commandFailed()),
-            this, SLOT(commandFailed()));
-    runningAction = ACT_NONE;
+    connect(runner, SIGNAL(commandCompleted(HgAction, QString)),
+            this, SLOT(commandCompleted(HgAction, QString)));
+    connect(runner, SIGNAL(commandFailed(HgAction, QString)),
+            this, SLOT(commandFailed(HgAction, QString)));
     statusBar()->addPermanentWidget(runner);
 
     setWindowTitle(tr("EasyMercurial"));
@@ -66,7 +65,7 @@
 
     readSettings();
 
-    tabPage = 0;
+//    tabPage = 0;
     justMerged = false;
     hgTabs = new HgTabWidget((QWidget *) this, remoteRepoPath, workFolderPath);
     setCentralWidget(hgTabs);
@@ -91,7 +90,7 @@
         open();
     }
 
-    hgPaths();
+    hgQueryPaths();
 }
 
 
@@ -147,168 +146,116 @@
 
 void MainWindow::hgStat()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        
-        params << "stat" << "-ardum";
-        
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_STAT;
-    }
+    QStringList params;
+    params << "stat" << "-ardum";
+    runner->requestAction(HgAction(ACT_STAT, workFolderPath, params));
 }
 
-void MainWindow::hgPaths()
+void MainWindow::hgQueryPaths()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        params << "paths";
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_PATHS;
-    }
+    QStringList params;
+    params << "paths";
+    runner->requestAction(HgAction(ACT_QUERY_PATHS, workFolderPath, params));
 }
 
-void MainWindow::hgBranch()
+void MainWindow::hgQueryBranch()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        params << "branch";
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_BRANCH;
-    }
+    QStringList params;
+    params << "branch";
+    runner->requestAction(HgAction(ACT_QUERY_BRANCH, workFolderPath, params));
 }
 
-void MainWindow::hgHeads()
+void MainWindow::hgQueryHeads()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        params << "heads";
-
-        //on empty repos, "hg heads" will fail, don't care of that.
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_HEADS;
-    }
+    QStringList params;
+    params << "heads";
+    // on empty repos, "hg heads" will fail -- we don't care about that.
+    runner->requestAction(HgAction(ACT_QUERY_HEADS, workFolderPath, params));
 }
 
 void MainWindow::hgLog()
 {
 //!!! This needs to be incremental, except when we pull or set new repo
-    if (runningAction == ACT_NONE)
+    QStringList params;
+    params << "log";
+    params << "--template";
+    params << "id: {rev}:{node|short}\\nauthor: {author}\\nbranch: {branches}\\ntag: {tag}\\ndatetime: {date|isodate}\\ntimestamp: {date|hgdate}\\nage: {date|age}\\nparents: {parents}\\ncomment: {desc|json}\\n\\n";
+    
+    runner->requestAction(HgAction(ACT_LOG, workFolderPath, params));
+}
+
+
+void MainWindow::hgQueryParents()
+{
+    QStringList params;
+    params << "parents";
+    runner->requestAction(HgAction(ACT_QUERY_PARENTS, workFolderPath, params));
+}
+
+void MainWindow::hgAnnotate()
+{
+    QStringList params;
+    QString currentFile;//!!! = hgTabs -> getCurrentFileListLine();
+    
+    if (!currentFile.isEmpty())
     {
-        QStringList params;
-        params << "log";
-        params << "--template";
-        params << "id: {rev}:{node|short}\\nauthor: {author}\\nbranch: {branches}\\ntag: {tag}\\ndatetime: {date|isodate}\\ntimestamp: {date|hgdate}\\nage: {date|age}\\nparents: {parents}\\ncomment: {desc|json}\\n\\n";
+        params << "annotate" << "--" << currentFile.mid(2);   //Jump over status marker characters (e.g "M ")
 
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_LOG;
+        runner->requestAction(HgAction(ACT_ANNOTATE, workFolderPath, params));
     }
 }
 
+void MainWindow::hgResolveMark()
+{
+    QStringList params;
+    QString currentFile;//!!! = hgTabs -> getCurrentFileListLine();
 
-void MainWindow::hgParents()
-{
-    if (runningAction == ACT_NONE)
+    if (!currentFile.isEmpty())
     {
-        QStringList params;
-        params << "parents";
+        params << "resolve" << "--mark" << "--" << currentFile.mid(2);   //Jump over status marker characters (e.g "M ")
 
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_PARENTS;
+        runner->requestAction(HgAction(ACT_RESOLVE_MARK, workFolderPath, params));
     }
 }
 
+void MainWindow::hgResolveList()
+{
+    QStringList params;
 
-void MainWindow::hgAnnotate()
+    params << "resolve" << "--list";
+    runner->requestAction(HgAction(ACT_RESOLVE_LIST, workFolderPath, params));
+}
+
+void MainWindow::hgAdd()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        QString currentFile;//!!! = hgTabs -> getCurrentFileListLine();
+    QStringList params;
 
-        if (!currentFile.isEmpty())
-        {
-            params << "annotate" << "--" << currentFile.mid(2);   //Jump over status marker characters (e.g "M ")
+    // hgExplorer permitted adding "all" files -- I'm not sure
+    // that one is a good idea, let's require the user to select
 
-            runner -> startHgCommand(workFolderPath, params);
-            runningAction = ACT_ANNOTATE;
-        }
+    QStringList files = hgTabs->getSelectedAddableFiles();
+
+    if (!files.empty()) {
+        params << "add" << "--" << files;
+        runner->requestAction(HgAction(ACT_ADD, workFolderPath, params));
     }
 }
 
 
-void MainWindow::hgResolveMark()
-{
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        QString currentFile;//!!! = hgTabs -> getCurrentFileListLine();
-
-        if (!currentFile.isEmpty())
-        {
-            params << "resolve" << "--mark" << "--" << currentFile.mid(2);   //Jump over status marker characters (e.g "M ")
-
-            runner -> startHgCommand(workFolderPath, params);
-            runningAction = ACT_RESOLVE_MARK;
-        }
-    }
-}
-
-
-
-void MainWindow::hgResolveList()
-{
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-
-        params << "resolve" << "--list";
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_RESOLVE_LIST;
-    }
-}
-
-
-
-void MainWindow::hgAdd()
-{
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-
-        // hgExplorer permitted adding "all" files -- I'm not sure
-        // that one is a good idea, let's require the user to select
-
-        QStringList files = hgTabs->getSelectedAddableFiles();
-
-        if (!files.empty()) {
-            params << "add" << "--" << files;
-            runner -> startHgCommand(workFolderPath, params);
-            runningAction = ACT_ADD;
-        }
-    }
-}
-
-
 void MainWindow::hgRemove()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        QStringList files = hgTabs->getSelectedRemovableFiles();
+    QStringList files = hgTabs->getSelectedRemovableFiles();
 
-        //!!! todo: confirmation dialog (with file list in it)
+    //!!! todo: confirmation dialog (with file list in it) (or do we
+    // need that? all it does is move the files to the removed
+    // list... doesn't it?)
 
-        if (!files.empty()) {
-
-            params << "remove" << "--after" << "--force" << "--" << files;
-            runner -> startHgCommand(workFolderPath, params);
-            runningAction = ACT_REMOVE;
-        }
+    if (!files.empty()) {
+        params << "remove" << "--after" << "--force" << "--" << files;
+        runner->requestAction(HgAction(ACT_REMOVE, workFolderPath, params));
+    }
 
 /*!!!
         QString currentFile;//!!! = hgTabs -> getCurrentFileListLine();
@@ -325,46 +272,34 @@
             }
         }
         */
-    }
 }
 
 void MainWindow::hgCommit()
 {
-    //!!! Now that hg actions can be fired asynchronously (e.g. from
-    // the filesystem modified callback) we _really_ need to be able to
-    // queue important actions rather than just ignore them if busy!
+    QStringList params;
+    QString comment;
 
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        QString comment;
+    QStringList files = hgTabs->getSelectedCommittableFiles();
+    if (files.empty()) files = hgTabs->getAllCommittableFiles();
 
-        QStringList files = hgTabs->getSelectedCommittableFiles();
-        if (files.empty()) files = hgTabs->getAllCommittableFiles();
+    if (ConfirmCommentDialog::confirmAndGetLongComment
+        (this,
+         tr("Commit files"),
+         tr("<h2>Commit files</h2><p>About to commit the following files:"),
+         tr("<h2>Commit files</h2><p>About to commit %1 files."),
+         files,
+         comment)) {
 
-        if (ConfirmCommentDialog::confirmAndComment(this,
-                                                    tr("Commit files"),
-                                                    tr("About to commit the following files:"),
-                                                    tr("About to commit %1 files."),
-                                                    files,
-                                                    comment,
-                                                    true)) {
-
-            //!!! do something more sensible when the comment is empty
-            // (i.e. tell the user about it?)
-
-            if ((justMerged == false) && //!!! review usage of 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();
-            }
-
-            runner -> startHgCommand(workFolderPath, params, false);
-            runningAction = ACT_COMMIT;
+        if ((justMerged == false) && //!!! review usage of justMerged, and how it interacts with asynchronous request queue
+            !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();
         }
+        
+        runner->requestAction(HgAction(ACT_COMMIT, workFolderPath, params));
     }
 }
 
@@ -387,23 +322,19 @@
 
 void MainWindow::hgTag()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        QString tag;
+    QStringList params;
+    QString tag;
 
-        if (ConfirmCommentDialog::confirmAndComment(this,
-                                                    tr("Tag"),
-                                                    tr("Enter tag:"),
-                                                    tag,
-                                                    false)) {
-            if (!tag.isEmpty()) //!!! do something better if it is empty
-            {
-                params << "tag" << "--user" << getUserInfo() << filterTag(tag);
-
-                runner -> startHgCommand(workFolderPath, params);
-                runningAction = ACT_TAG;
-            }
+    if (ConfirmCommentDialog::confirmAndGetShortComment
+        (this,
+         tr("Tag"),
+         tr("Enter tag:"),
+         tag)) {
+        if (!tag.isEmpty()) //!!! do something better if it is empty
+        {
+            params << "tag" << "--user" << getUserInfo() << filterTag(tag);
+            
+            runner->requestAction(HgAction(ACT_TAG, workFolderPath, params));
         }
     }
 }
@@ -411,37 +342,34 @@
 
 void MainWindow::hgIgnore()
 {
-    if (runningAction == ACT_NONE)
+    QString hgIgnorePath;
+    QStringList params;
+    QString editorName;
+
+    hgIgnorePath = workFolderPath;
+    hgIgnorePath += ".hgignore";
+    
+    params << hgIgnorePath;
+    
+    if ((getSystem() == "Linux"))
     {
-        QString hgIgnorePath;
-        QStringList params;
-        QString editorName;
+        editorName = "gedit";
+    }
+    else
+    {
+        editorName = """C:\\Program Files\\Windows NT\\Accessories\\wordpad.exe""";
+    }
 
-        hgIgnorePath = workFolderPath;
-        hgIgnorePath += ".hgignore";
+    HgAction action(ACT_HG_IGNORE, workFolderPath, params);
+    action.executable = editorName;
 
-        params << hgIgnorePath;
-
-        if ((getSystem() == "Linux"))
-        {
-            editorName = "gedit";
-        }
-        else
-        {
-            editorName = """C:\\Program Files\\Windows NT\\Accessories\\wordpad.exe""";
-        }
-
-        runner -> startCommand(editorName, workFolderPath, params);
-        runningAction = ACT_HG_IGNORE;
-    }
+    runner->requestAction(action);
 }
 
 
 
 void MainWindow::hgFileDiff()
 {
-    if (runningAction == ACT_NONE)
-    {
         QStringList params;
 /*!!!
         QString currentFile = hgTabs -> getCurrentFileListLine();
@@ -454,28 +382,25 @@
             runningAction = ACT_FILEDIFF;
         }
     */
-    }
 }
 
 
 void MainWindow::hgFolderDiff()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        //Diff parent against working folder (folder diff)
-        params << "kdiff3";
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_FOLDERDIFF;
-    }
+    //Diff parent against working folder (folder diff)
+    params << "--config" << "extensions.extdiff=" << "extdiff" << "-p";
+
+    params << "kompare";
+
+//    params << "kdiff3";
+    runner->requestAction(HgAction(ACT_FOLDERDIFF, workFolderPath, params));
 }
 
 
 void MainWindow::hgChgSetDiff()
 {
-    if (runningAction == ACT_NONE)
-    {
         QStringList params;
 
         //Diff 2 history log versions against each other
@@ -495,29 +420,22 @@
             QMessageBox::information(this, tr("Changeset diff"), tr("Please select two changesets from history list or heads list first."));
         }
         */
-    }
 }
 
 
 
 void MainWindow::hgUpdate()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        params << "update";
-
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_UPDATE;
-    }
+    params << "update";
+    
+    runner->requestAction(HgAction(ACT_UPDATE, workFolderPath, params));
 }
 
 
 void MainWindow::hgUpdateToRev()
 {
-    if (runningAction == ACT_NONE)
-    {
         QStringList params;
         QString rev;
 /*!!!
@@ -532,140 +450,113 @@
 
         runningAction = ACT_UPDATE;
         */
-    }
+
 }
 
 
 void MainWindow::hgRevert()
 {
-    if (runningAction == ACT_NONE)
-    {
-        //!!! todo: ask user!
+    QStringList params;
+    QString comment;
 
-        QStringList params;
-
-        QStringList files = hgTabs->getSelectedCommittableFiles();
-
+    QStringList files = hgTabs->getSelectedRevertableFiles();
+    if (files.empty()) files = hgTabs->getAllRevertableFiles();
+    
+    if (ConfirmCommentDialog::confirmDangerousFilesAction
+        (this,
+         tr("Revert files"),
+         tr("<h2>Revert files</h2><p>About to revert the following files to their previous committed state.  This will <b>throw away any changes</b> that you have made to these files but have not committed."),
+         tr("<h2>Revert files</h2><p>About to revert %1 files.  This will <b>throw away any changes</b> that you have made to these files but have not committed."),
+         files)) {
+        
         if (files.empty()) {
             params << "revert" << "--no-backup";
         } else {
             params << "revert" << "--no-backup" << "--" << files;
         }
-
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_REVERT;
+        
+        runner->requestAction(HgAction(ACT_REVERT, workFolderPath, params));
     }
 }
 
 void MainWindow::hgRetryMerge()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        params << "resolve" << "--all";
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_RETRY_MERGE;
-    }
+    params << "resolve" << "--all";
+    runner->requestAction(HgAction(ACT_RETRY_MERGE, workFolderPath, params));
 }
 
 
 void MainWindow::hgMerge()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        params << "merge";
-
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_MERGE;
-    }
+    params << "merge";
+    
+    runner->requestAction(HgAction(ACT_MERGE, workFolderPath, params));
 }
 
 
 void MainWindow::hgCloneFromRemote()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        if (!QDir(workFolderPath).exists()) {
-            if (!QDir().mkpath(workFolderPath)) {
-                DEBUG << "hgCloneFromRemote: Failed to create target path "
-                        << workFolderPath << endl;
-                //!!! report error
-                return;
-            }
+    if (!QDir(workFolderPath).exists()) {
+        if (!QDir().mkpath(workFolderPath)) {
+            DEBUG << "hgCloneFromRemote: Failed to create target path "
+                  << workFolderPath << endl;
+            //!!! report error
+            return;
         }
+    }
 
-        params << "clone" << remoteRepoPath << workFolderPath;
+    params << "clone" << remoteRepoPath << workFolderPath;
+    
+    hgTabs->setWorkFolderAndRepoNames(workFolderPath, remoteRepoPath);
 
-        hgTabs->setWorkFolderAndRepoNames(workFolderPath, remoteRepoPath);
-
-        runner -> startHgCommand(workFolderPath, params, true);
-        runningAction = ACT_CLONEFROMREMOTE;
-    }
+    runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, workFolderPath, params));
 }
 
-
 void MainWindow::hgInit()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        params << "init";
-        params << workFolderPath;
+    params << "init";
+    params << workFolderPath;
 
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_INIT;
-    }
+    runner->requestAction(HgAction(ACT_INIT, workFolderPath, params));
 }
 
-
 void MainWindow::hgIncoming()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        params << "incoming" << "--newest-first" << remoteRepoPath;
+    params << "incoming" << "--newest-first" << remoteRepoPath;
 
-        runner -> startHgCommand(workFolderPath, params, true);
-        runningAction = ACT_INCOMING;
-    }
+    runner->requestAction(HgAction(ACT_INCOMING, workFolderPath, params));
 }
 
 
 void MainWindow::hgPull()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        params << "pull" << remoteRepoPath;
+    params << "pull" << remoteRepoPath;
 
-        runner -> startHgCommand(workFolderPath, params, true);
-        runningAction = ACT_PULL;
-    }
+    runner->requestAction(HgAction(ACT_PULL, workFolderPath, params));
 }
 
 
 void MainWindow::hgPush()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
+    QStringList params;
 
-        params << "push" << remoteRepoPath;
+    params << "push" << remoteRepoPath;
 
-        runner -> startHgCommand(workFolderPath, params, true);
-        runningAction = ACT_PUSH;
-    }
+    runner->requestAction(HgAction(ACT_PUSH, workFolderPath, params));
 }
 
-
 QString MainWindow::listAllUpIpV4Addresses()
 {
     QString ret;
@@ -697,30 +588,24 @@
 
 void MainWindow::hgServe()
 {
-    if (runningAction == ACT_NONE)
-    {
-        QStringList params;
-        QString msg;
+    QStringList params;
+    QString msg;
 
-        QString addrs = listAllUpIpV4Addresses();
-        QTextStream(&msg) << "Server running on address(es) (" << addrs << "), port 8000";
-        params << "serve";
+    QString addrs = listAllUpIpV4Addresses();
+    QTextStream(&msg) << "Server running on address(es) (" << addrs << "), port 8000";
+    params << "serve";
 
-        runner -> startHgCommand(workFolderPath, params);
-        runningAction = ACT_SERVE;
-
-        QMessageBox::information(this, "Serve", msg, QMessageBox::Close);
-        runner -> killCurrentCommand();
-    }
+    runner->requestAction(HgAction(ACT_SERVE, workFolderPath, params));
+    
+    QMessageBox::information(this, "Serve", msg, QMessageBox::Close);
+//!!!    runner -> killCurrentCommand();
 }
 
-
 void MainWindow::startupDialog()
 {
     StartupDialog *dlg = new StartupDialog(this);
     if (dlg->exec()) firstStart = false;
 }
-    
 
 void MainWindow::open()
 {
@@ -767,7 +652,7 @@
 
             if (result) {
                 enableDisableActions();
-                hgPaths();
+                hgQueryPaths();
                 done = true;
             }
 
@@ -1096,175 +981,149 @@
     hgStat();
 }
 
-void MainWindow::commandFailed()
+void MainWindow::commandFailed(HgAction action, QString stderr)
 {
     DEBUG << "MainWindow::commandFailed" << endl;
-    runningAction = ACT_NONE;
-    runner -> hideProgBar();
+//    runner -> hideProgBar();
 
     //!!! N.B hg incoming returns 1 even if successful, if there were no changes
 }
 
-void MainWindow::commandCompleted()
+void MainWindow::commandCompleted(HgAction completedAction, QString output)
 {
     bool shouldHgStat = false;
 
-    if (runningAction != ACT_NONE)
+    HGACTIONS action = completedAction.action;
+
+    if (action == ACT_NONE) return;
+
+    switch(action) {
+
+    case ACT_QUERY_PATHS:
     {
-        //We are running some hg command...
-        if (runner -> isCommandRunning() == false)
-        {
-            //Running has just ended.
-            int exitCode = runner -> getExitCode();
+        DEBUG << "stdout is " << output << endl;
+        LogParser lp(output, "=");
+        LogList ll = lp.parse();
+        DEBUG << ll.size() << " results" << endl;
+        if (!ll.empty()) {
+            remoteRepoPath = lp.parse()[0]["default"].trimmed();
+            DEBUG << "Set remote path to " << remoteRepoPath << endl;
+        }
+        MultiChoiceDialog::addRecentArgument("local", workFolderPath);
+        MultiChoiceDialog::addRecentArgument("remote", remoteRepoPath);
+        hgTabs->setWorkFolderAndRepoNames(workFolderPath, remoteRepoPath);
+        enableDisableActions();
+        break;
+    }
 
-            runner -> hideProgBar();
+    case ACT_QUERY_BRANCH:
+        currentBranch = output.trimmed();
+        hgTabs->setBranch(currentBranch);
+        break;
 
-            //Clumsy...
-            if ((EXITOK(exitCode)) || ((exitCode == 1) && (runningAction == ACT_INCOMING)))
-            {
-                QString output = runner->getOutput();
+    case ACT_STAT:
+        hgTabs -> updateWorkFolderFileList(output);
+        updateFileSystemWatcher();
+        break;
+        
+    case ACT_INCOMING:
+    case ACT_ANNOTATE:
+    case ACT_RESOLVE_LIST:
+    case ACT_RESOLVE_MARK:
+        presentLongStdoutToUser(output);
+        shouldHgStat = true;
+        break;
+        
+    case ACT_PULL:
+        QMessageBox::information(this, "Pull", output);
+        shouldHgStat = true;
+        break;
+        
+    case ACT_PUSH:
+        QMessageBox::information(this, "Push", output);
+        shouldHgStat = true;
+        break;
+        
+    case ACT_INIT:
+        MultiChoiceDialog::addRecentArgument("init", workFolderPath);
+        MultiChoiceDialog::addRecentArgument("local", workFolderPath);
+        enableDisableActions();
+        shouldHgStat = true;
+        break;
+        
+    case ACT_CLONEFROMREMOTE:
+        MultiChoiceDialog::addRecentArgument("local", workFolderPath);
+        MultiChoiceDialog::addRecentArgument("remote", remoteRepoPath);
+        MultiChoiceDialog::addRecentArgument("remote", workFolderPath, true);
+        QMessageBox::information(this, "Clone", output);
+        enableDisableActions();
+        shouldHgStat = true;
+        break;
+        
+    case ACT_LOG:
+        hgTabs -> updateLocalRepoHgLogList(output);
+        break;
+        
+    case ACT_QUERY_PARENTS:
+        foreach (Changeset *cs, currentParents) delete cs;
+        currentParents = Changeset::parseChangesets(output);
+        break;
+        
+    case ACT_QUERY_HEADS:
+        foreach (Changeset *cs, currentHeads) delete cs;
+        currentHeads = Changeset::parseChangesets(output);
+        break;
+        
+    case ACT_REMOVE:
+    case ACT_ADD:
+    case ACT_COMMIT:
+    case ACT_FILEDIFF:
+    case ACT_FOLDERDIFF:
+    case ACT_CHGSETDIFF:
+    case ACT_REVERT:
+    case ACT_SERVE:
+    case ACT_TAG:
+    case ACT_HG_IGNORE:
+        shouldHgStat = true;
+        break;
+        
+    case ACT_UPDATE:
+        QMessageBox::information(this, tr("Update"), output);
+        shouldHgStat = true;
+        break;
+        
+    case ACT_MERGE:
+        QMessageBox::information(this, tr("Merge"), output);
+        shouldHgStat = true;
+        justMerged = true;
+        break;
+        
+    case ACT_RETRY_MERGE:
+        QMessageBox::information(this, tr("Merge retry"), tr("Merge retry successful."));
+        shouldHgStat = true;
+        justMerged = true;
+        break;
+        
+    default:
+        break;
+    }
 
-                //Successful running.
-                switch(runningAction)
-                {
-                    case ACT_PATHS:
-                    {
-                        DEBUG << "stdout is " << output << endl;
-                        LogParser lp(output, "=");
-                        LogList ll = lp.parse();
-                        DEBUG << ll.size() << " results" << endl;
-                        if (!ll.empty()) {
-                            remoteRepoPath = lp.parse()[0]["default"].trimmed();
-                            DEBUG << "Set remote path to " << remoteRepoPath << endl;
-                        }
-                        MultiChoiceDialog::addRecentArgument("local", workFolderPath);
-                        MultiChoiceDialog::addRecentArgument("remote", remoteRepoPath);
-                        hgTabs->setWorkFolderAndRepoNames(workFolderPath, remoteRepoPath);
-                        enableDisableActions();
-                        break;
-                    }
+    enableDisableActions();
 
-                    case ACT_BRANCH:
-                        currentBranch = output.trimmed();
-                        hgTabs->setBranch(currentBranch);
-                        break;
-
-                    case ACT_STAT:
-                        hgTabs -> updateWorkFolderFileList(output);
-                        updateFileSystemWatcher();
-                        break;
-
-                    case ACT_INCOMING:
-                    case ACT_ANNOTATE:
-                    case ACT_RESOLVE_LIST:
-                    case ACT_RESOLVE_MARK:
-                        presentLongStdoutToUser(output);
-                        shouldHgStat = true;
-                        break;
-
-                    case ACT_PULL:
-                        QMessageBox::information(this, "Pull", output);
-                        shouldHgStat = true;
-                        break;
-
-                    case ACT_PUSH:
-                        QMessageBox::information(this, "Push", output);
-                        shouldHgStat = true;
-                        break;
-
-                    case ACT_INIT:
-                        MultiChoiceDialog::addRecentArgument("init", workFolderPath);
-                        MultiChoiceDialog::addRecentArgument("local", workFolderPath);
-                        enableDisableActions();
-                        shouldHgStat = true;
-                        break;
-
-                    case ACT_CLONEFROMREMOTE:
-                        MultiChoiceDialog::addRecentArgument("local", workFolderPath);
-                        MultiChoiceDialog::addRecentArgument("remote", remoteRepoPath);
-                        MultiChoiceDialog::addRecentArgument("remote", workFolderPath, true);
-                        QMessageBox::information(this, "Clone", output);
-                        enableDisableActions();
-                        shouldHgStat = true;
-                        break;
-
-                    case ACT_LOG:
-                        hgTabs -> updateLocalRepoHgLogList(output);
-                        break;
-
-                    case ACT_PARENTS:
-                        foreach (Changeset *cs, currentParents) delete cs;
-                        currentParents = Changeset::parseChangesets(output);
-                        break;
-
-                    case ACT_HEADS:
-                        foreach (Changeset *cs, currentHeads) delete cs;
-                        currentHeads = Changeset::parseChangesets(output);
-                        break;
-
-                    case ACT_REMOVE:
-                    case ACT_ADD:
-                    case ACT_COMMIT:
-                    case ACT_FILEDIFF:
-                    case ACT_FOLDERDIFF:
-                    case ACT_CHGSETDIFF:
-                    case ACT_REVERT:
-                    case ACT_SERVE:
-                    case ACT_TAG:
-                    case ACT_HG_IGNORE:
-                        shouldHgStat = true;
-                        break;
-
-                    case ACT_UPDATE:
-                        QMessageBox::information(this, tr("Update"), output);
-                        shouldHgStat = true;
-                        break;
-
-                    case ACT_MERGE:
-                        QMessageBox::information(this, tr("Merge"), output);
-                        shouldHgStat = true;
-                        justMerged = true;
-                        break;
-
-                    case ACT_RETRY_MERGE:
-                        QMessageBox::information(this, tr("Merge retry"), tr("Merge retry successful."));
-                        shouldHgStat = true;
-                        justMerged = true;
-                        break;
-
-                    default:
-                        break;
-                }
-            }
-
-
-            //Typical sequence goes paths -> branch -> stat -> heads -> parents -> log
-            if (runningAction == ACT_PATHS)
-            {
-                runningAction = ACT_NONE;
-                hgBranch();
-            }
-            else if (runningAction == ACT_BRANCH)
-            {
-                runningAction = ACT_NONE;
-                hgStat();
-            }
-            else if (runningAction == ACT_STAT)
-            {
-                runningAction = ACT_NONE;
-                hgHeads();
-            }
-            else if (runningAction == ACT_HEADS)
-            {
-                runningAction = ACT_NONE;
-                hgParents();
-            }
-            else if (runningAction == ACT_PARENTS)
-            {
-                runningAction = ACT_NONE;
-                hgLog();
-            }
-            else if ((runningAction == ACT_MERGE) && (exitCode != 0))
+    // Typical sequence goes paths -> branch -> stat -> heads -> parents -> log
+    if (action == ACT_QUERY_PATHS) {
+        hgQueryBranch();
+    } else if (action == ACT_QUERY_BRANCH) {
+        hgStat();
+    } else if (action == ACT_STAT) {
+        hgQueryHeads();
+    } else if (action == ACT_QUERY_HEADS) {
+        hgQueryParents();
+    } else if (action == ACT_QUERY_PARENTS) {
+        hgLog();
+    } else 
+/* Move to commandFailed
+if ((runningAction == ACT_MERGE) && (exitCode != 0))
             {
                 // If we had a failed merge, offer to retry
                 if (QMessageBox::Ok == QMessageBox::information(this, tr("Retry merge ?"), tr("Merge attempt failed. retry ?"), QMessageBox::Ok | QMessageBox::Cancel))
@@ -1280,16 +1139,10 @@
             }
             else
             {
-                runningAction = ACT_NONE;
-                if (shouldHgStat)
-                {
-                    hgPaths();
-                }
-            }
+*/
+        if (shouldHgStat) {
+            hgQueryPaths();
         }
-    }
-
-    enableDisableActions();
 }
 
 void MainWindow::connectActions()
@@ -1298,8 +1151,7 @@
     connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
     connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
 
-    connect(hgStatAct, SIGNAL(triggered()), this, SLOT(hgPaths()));
-    connect(hgTabs, SIGNAL(workFolderViewTypesChanged()), this, SLOT(hgPaths()));
+    connect(hgStatAct, SIGNAL(triggered()), this, SLOT(hgQueryPaths()));
     connect(hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove()));
     connect(hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd()));
     connect(hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit()));
@@ -1322,7 +1174,7 @@
     connect(hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull()));
     connect(hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush()));
 
-    connect(hgTabs, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
+//    connect(hgTabs, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
 
     connect(hgUpdateToRevAct, SIGNAL(triggered()), this, SLOT(hgUpdateToRev()));
     connect(hgAnnotateAct, SIGNAL(triggered()), this, SLOT(hgAnnotate()));
@@ -1331,13 +1183,13 @@
     connect(hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe()));
     connect(clearSelectionsAct, SIGNAL(triggered()), this, SLOT(clearSelections()));
 }
-
+/*!!!
 void MainWindow::tabChanged(int currTab)
 {
     tabPage = currTab;
 
 }
-
+*/
 void MainWindow::enableDisableActions()
 {
     DEBUG << "MainWindow::enableDisableActions" << endl;
@@ -1448,112 +1300,6 @@
     }
     hgMergeAct->setEnabled(localRepoActionsEnabled && canMerge);
     hgUpdateAct->setEnabled(localRepoActionsEnabled && canUpdate);
-
-/*!!!
-    int added, modified, removed, notTracked, selected, selectedAdded, selectedModified, selectedRemoved, selectedNotTracked;
-
-    countModifications(hgTabs -> workFolderFileList,
-        added, modified, removed, notTracked,
-        selected,
-        selectedAdded, selectedModified, selectedRemoved, selectedNotTracked);
-
-    if (tabPage == WORKTAB)
-    {
-        //Enable / disable actions according to workFolderFileList selections / currentSelection / count
-        hgChgSetDiffAct -> setEnabled(false);
-        hgUpdateToRevAct -> setEnabled(false);
-
-        if (localRepoActionsEnabled)
-        {
-            if ((added == 0) && (modified == 0) && (removed == 0))
-            {
-                hgCommitAct -> setEnabled(false);
-                hgRevertAct -> setEnabled(false);
-            }
-            else if (selected != 0)
-            {
-                if (selectedNotTracked != 0)
-                {
-                    hgCommitAct -> setEnabled(false);
-                }
-                else if ((selectedAdded == 0) && (selectedModified == 0) && (selectedRemoved == 0))
-                {
-                    hgCommitAct -> setEnabled(false);
-                }
-            }
-
-            if (modified == 0)
-            {
-                hgFolderDiffAct -> setEnabled(false);
-            }
-
-            if (!isSelectedModified(hgTabs -> workFolderFileList))
-            {
-                hgFileDiffAct -> setEnabled(false);
-                hgRevertAct -> setEnabled(false);
-            }
-
-            //JK 14.5.2010: Fixed confusing add button. Now this is simple: If we have something to add (any non-tracked files), add is enabled.
-            if (notTracked == 0)
-            {
-                hgAddAct -> setEnabled(false);
-            }
-
-            if (!isSelectedDeletable(hgTabs -> workFolderFileList))
-            {
-                hgRemoveAct -> setEnabled(false);
-            }
-
-            hgResolveListAct -> setEnabled(true);
-
-            if (hgTabs -> localRepoHeadsList->count() < 2)
-            {
-                hgMergeAct -> setEnabled(false);
-                hgRetryMergeAct -> setEnabled(false);
-            }
-
-            if (hgTabs -> localRepoHeadsList->count() < 1)
-            {
-                hgTagAct -> setEnabled(false);
-            }
-
-            QString currentFile = hgTabs -> getCurrentFileListLine();
-            if (!currentFile.isEmpty())
-            {
-                hgAnnotateAct -> setEnabled(true);
-                hgResolveMarkAct -> setEnabled(true);
-            }
-            else
-            {
-                hgAnnotateAct -> setEnabled(false);
-                hgResolveMarkAct -> setEnabled(false);
-            }
-        }
-    }
-    else
-    {
-        QList <QListWidgetItem *> headSelList = hgTabs -> localRepoHeadsList->selectedItems();
-        QList <QListWidgetItem *> historySelList = hgTabs -> localRepoHgLogList->selectedItems();
-
-        if ((historySelList.count() == 2) || (headSelList.count() == 2))
-        {
-            hgChgSetDiffAct -> setEnabled(true);
-        }
-        else
-        {
-            hgChgSetDiffAct -> setEnabled(false);
-        }
-
-        if (historySelList.count() == 1)
-        {
-            hgUpdateToRevAct -> setEnabled(true);
-        }
-        else
-        {
-            hgUpdateToRevAct -> setEnabled(false);
-        }
-    }
-    */
 }
 
 void MainWindow::createActions()
@@ -1752,7 +1498,7 @@
     QSize size = settings.value("size", QSize(400, 400)).toSize();
     firstStart = settings.value("firststart", QVariant(true)).toBool();
 
-    initialFileTypesBits = (unsigned char) settings.value("viewFileTypes", QVariant(DEFAULT_HG_STAT_BITS)).toInt();
+//!!!    initialFileTypesBits = (unsigned char) settings.value("viewFileTypes", QVariant(DEFAULT_HG_STAT_BITS)).toInt();
     resize(size);
     move(pos);
 }
--- a/mainwindow.h	Thu Nov 25 21:08:17 2010 +0000
+++ b/mainwindow.h	Fri Nov 26 12:48:29 2010 +0000
@@ -22,6 +22,7 @@
 #include "hgrunner.h"
 #include "common.h"
 #include "changeset.h"
+#include "hgaction.h"
 
 #include <QMainWindow>
 #include <QListWidget>
@@ -32,39 +33,6 @@
 class QMenu;
 QT_END_NAMESPACE
 
-enum HGACTIONS
-{
-    ACT_NONE,
-    ACT_PATHS,
-    ACT_BRANCH,
-    ACT_STAT,
-    ACT_HEADS,
-    ACT_PARENTS,
-    ACT_LOG,
-    ACT_REMOVE,
-    ACT_ADD,
-    ACT_INCOMING,
-    ACT_PUSH,
-    ACT_PULL,
-    ACT_CLONEFROMREMOTE,
-    ACT_INIT,
-    ACT_COMMIT,
-    ACT_ANNOTATE,
-    ACT_FILEDIFF,
-    ACT_FOLDERDIFF,
-    ACT_CHGSETDIFF,
-    ACT_UPDATE,
-    ACT_REVERT,
-    ACT_MERGE,
-    ACT_RESOLVE_LIST,
-    ACT_SERVE,
-    ACT_RESOLVE_MARK,
-    ACT_RETRY_MERGE,
-    ACT_TAG,
-    ACT_HG_IGNORE,
-};
-
-
 class MainWindow : public QMainWindow
 {
     Q_OBJECT
@@ -86,11 +54,11 @@
     void closeEvent(QCloseEvent *event);
 
 public slots:
-    void hgPaths();
+    void hgQueryPaths();
     void hgStat();
-    void tabChanged(int currTab);
-    void commandCompleted();
-    void commandFailed();
+//    void tabChanged(int currTab);
+    void commandCompleted(HgAction action, QString stdout);
+    void commandFailed(HgAction action, QString stdout);
     void enableDisableActions();
 
 private slots:
@@ -127,9 +95,9 @@
     void fsFileChanged(QString);
 
 private:
-    void hgBranch();
-    void hgHeads();
-    void hgParents();
+    void hgQueryBranch();
+    void hgQueryHeads();
+    void hgQueryParents();
     void hgLog();
     void createActions();
     void connectActions();
@@ -216,13 +184,12 @@
     QToolBar *repoToolBar;
     QToolBar *workFolderToolBar;
 
-    HGACTIONS   runningAction;
-    HgRunner    *runner;
+    HgRunner *runner;
 
     QFileSystemWatcher *fsWatcher;
 
-    int             tabPage;
-    unsigned char   initialFileTypesBits;
+//    int             tabPage;
+//    unsigned char   initialFileTypesBits;
     bool            justMerged;
 };