changeset 113:5fc7b4fc77a8

* Better error handling/reporting; some futile changes to termios handling; avoid weirdly stretching panned view in panner
author Chris Cannam
date Fri, 26 Nov 2010 21:04:40 +0000
parents 4bd17f36d059
children bb2d2eecdd60
files hgaction.h hgrunner.cpp hgrunner.h mainwindow.cpp panner.cpp panner.h
diffstat 6 files changed, 160 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/hgaction.h	Fri Nov 26 17:02:55 2010 +0000
+++ b/hgaction.h	Fri Nov 26 21:04:40 2010 +0000
@@ -58,8 +58,7 @@
     HGACTIONS action;
     QString workingDir;
     QStringList params;
-
-    QString executable; // empty for normal Hg
+    QString executable; // empty for normal Hg, but gets filled in by hgrunner
 
     HgAction() : action(ACT_NONE) { }
 
--- a/hgrunner.cpp	Fri Nov 26 17:02:55 2010 +0000
+++ b/hgrunner.cpp	Fri Nov 26 21:04:40 2010 +0000
@@ -34,30 +34,17 @@
 
 #ifndef Q_OS_WIN32
 #include <unistd.h>
+#include <termios.h>
 #include <fcntl.h>
 #endif
 
 HgRunner::HgRunner(QWidget * parent): QProgressBar(parent)
 {
-    m_proc = new QProcess(this);
-
-    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
-    env.insert("LANG", "en_US.utf8");
-    env.insert("LC_ALL", "en_US.utf8");
-    env.insert("HGPLAIN", "1");
-    m_proc->setProcessEnvironment(env);
+    m_proc = 0;
 
     setTextVisible(false);
     setVisible(false);
     m_isRunning = false;
-
-    connect(m_proc, SIGNAL(started()), this, SLOT(started()));
-    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()));
 }
 
 HgRunner::~HgRunner()
@@ -124,7 +111,7 @@
 
 void HgRunner::getUsername()
 {
-    if (m_procInput) {
+    if (m_ptyFile) {
         bool ok = false;
         QString prompt = tr("User name:");
         if (m_realm != "") {
@@ -135,8 +122,8 @@
             tr("Enter user name"), prompt,
             QLineEdit::Normal, QString(), &ok);
         if (ok) {
-            m_procInput->write(QString("%1\n").arg(pwd).toUtf8());
-            m_procInput->flush();
+            m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
+            m_ptyFile->flush();
             return;
         } else {
             DEBUG << "HgRunner::getUsername: user cancelled" << endl;
@@ -151,7 +138,7 @@
 
 void HgRunner::getPassword()
 {
-    if (m_procInput) {
+    if (m_ptyFile) {
         bool ok = false;
         QString prompt = tr("Password:");
         if (m_userName != "") {
@@ -168,8 +155,8 @@
             tr("Enter password"), prompt,
              QLineEdit::Password, QString(), &ok);
         if (ok) {
-            m_procInput->write(QString("%1\n").arg(pwd).toUtf8());
-            m_procInput->flush();
+            m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8());
+            m_ptyFile->flush();
             return;
         } else {
             DEBUG << "HgRunner::getPassword: user cancelled" << endl;
@@ -182,7 +169,7 @@
     killCurrentCommand();
 }
 
-void HgRunner::checkPrompts(QString chunk)
+bool HgRunner::checkPrompts(QString chunk)
 {
     //DEBUG << "checkPrompts: " << chunk << endl;
 
@@ -190,11 +177,11 @@
     QString lower = text.toLower();
     if (lower.endsWith("password:")) {
         getPassword();
-        return;
+        return true;
     }
     if (lower.endsWith("user:")) {
         getUsername();
-        return;
+        return true;
     }
     QRegExp userRe("\\buser:\\s*([^\\s]+)");
     if (userRe.indexIn(text) >= 0) {
@@ -204,22 +191,36 @@
     if (realmRe.indexIn(text) >= 0) {
         noteRealm(realmRe.cap(1));
     }
+    return false;
 }
 
 void HgRunner::dataReadyStdout()
 {
     DEBUG << "dataReadyStdout" << endl;
     QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput());
-    m_stdout += chunk;
-    checkPrompts(chunk);
+    if (!checkPrompts(chunk)) {
+        m_stdout += chunk;
+    }
 }
 
 void HgRunner::dataReadyStderr()
 {
     DEBUG << "dataReadyStderr" << endl;
     QString chunk = QString::fromUtf8(m_proc->readAllStandardError());
-    m_stderr += chunk;
-    checkPrompts(chunk);
+    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::finished(int procExitCode, QProcess::ExitStatus procExitStatus)
@@ -235,18 +236,23 @@
     m_currentAction = HgAction();
 
     closeProcInput();
+    delete m_proc;
+    m_proc = 0;
 
     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(completedAction, m_stdout);
     } else {
-        DEBUG << "HgRunner::finished: Command failed, stderr follows" << endl;
-        DEBUG << m_stderr << endl;
-        emit commandFailed(completedAction, m_stderr);
+        if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) {
+            DEBUG << "HgRunner::finished: Command completed successfully"
+                  << 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();
@@ -255,7 +261,8 @@
 void HgRunner::killCurrentCommand()
 {
     if (m_isRunning) {
-        m_proc -> kill();
+        m_currentAction.action = ACT_NONE; // so that we don't bother to notify
+        m_proc->kill();
     }
 }
 
@@ -306,6 +313,22 @@
     m_realm = "";
     m_userName = "";
 
+    m_proc = new QProcess;
+
+    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+    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(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()));
+
     if (!action.workingDir.isEmpty()) {
         m_proc->setWorkingDirectory(action.workingDir);
     }
@@ -313,7 +336,10 @@
     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);
         }
     }
 
@@ -324,6 +350,10 @@
 
     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;
     
     m_proc->start(executable, params);
@@ -347,6 +377,16 @@
         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");
     }
@@ -360,8 +400,11 @@
         return;
     }
     m_ptyMasterFd = master;
-    m_procInput = new QFile();
-    m_procInput->open(m_ptyMasterFd, QFile::WriteOnly);
+    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;
@@ -372,8 +415,8 @@
 {
 #ifndef Q_OS_WIN32
     if (m_ptySlaveFilename != "") {
-        delete m_procInput;
-        m_procInput = 0;
+        delete m_ptyFile;
+        m_ptyFile = 0;
         ::close(m_ptyMasterFd);
         m_ptySlaveFilename = "";
     }
--- a/hgrunner.h	Fri Nov 26 17:02:55 2010 +0000
+++ b/hgrunner.h	Fri Nov 26 21:04:40 2010 +0000
@@ -52,6 +52,7 @@
     void finished(int procExitCode, QProcess::ExitStatus procExitStatus);
     void dataReadyStdout();
     void dataReadyStderr();
+    void dataReadyPty();
 
 private:
     void checkQueue();
@@ -64,7 +65,7 @@
     void noteRealm(QString);
     void getUsername();
     void getPassword();
-    void checkPrompts(QString);
+    bool checkPrompts(QString);
 
     void openTerminal();
     void closeTerminal();
@@ -72,7 +73,7 @@
     int m_ptyMasterFd;
     int m_ptySlaveFd;
     QString m_ptySlaveFilename;
-    QFile *m_procInput;
+    QFile *m_ptyFile;
     
     bool m_isRunning;
     QProcess *m_proc;
--- a/mainwindow.cpp	Fri Nov 26 17:02:55 2010 +0000
+++ b/mainwindow.cpp	Fri Nov 26 21:04:40 2010 +0000
@@ -540,6 +540,7 @@
     params << "clone" << remoteRepoPath << workFolderPath;
     
     hgTabs->setWorkFolderAndRepoNames(workFolderPath, remoteRepoPath);
+    hgTabs->updateWorkFolderFileList("");
 
     runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, workFolderPath, params));
 }
@@ -1010,9 +1011,46 @@
 void MainWindow::commandFailed(HgAction action, QString stderr)
 {
     DEBUG << "MainWindow::commandFailed" << endl;
-//    runner -> hideProgBar();
 
-    //!!! N.B hg incoming returns 1 even if successful, if there were no changes
+    // Some commands we just have to ignore bad return values from:
+
+    switch(action.action) {
+    case ACT_NONE:
+        // uh huh
+        return;
+    case ACT_INCOMING:
+        // returns non-zero code if the check was successful but there
+        // are no changes pending
+        return;
+    case ACT_FOLDERDIFF:
+    case ACT_FILEDIFF:
+    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;
+
+    default:
+    }
+
+    QString command = action.executable;
+    if (command == "") command = "hg";
+    foreach (QString arg, action.params) {
+        command += " " + arg;
+    }
+
+    QString message = tr("<qt><h3>Command failed</h3>"
+                         "<p>The following command failed:</p>"
+                         "<code>%1</code>"
+                         "%2</qt>")
+        .arg(command)
+        .arg((stderr.trimmed() != "") ?
+             tr("<p>Its output said:</p><code>%1</code>")
+             .arg(xmlEncode(stderr.left(800))
+                  .replace("\n", "<br>"))
+             : "");
+
+    QMessageBox::warning(this, tr("Command failed"), message);
 }
 
 void MainWindow::commandCompleted(HgAction completedAction, QString output)
@@ -1048,7 +1086,7 @@
         break;
 
     case ACT_STAT:
-        hgTabs -> updateWorkFolderFileList(output);
+        hgTabs->updateWorkFolderFileList(output);
         updateFileSystemWatcher();
         break;
         
--- a/panner.cpp	Fri Nov 26 17:02:55 2010 +0000
+++ b/panner.cpp	Fri Nov 26 21:04:40 2010 +0000
@@ -17,6 +17,7 @@
 
 #include "panner.h"
 #include "panned.h"
+#include "debug.h"
 
 #include <QPolygon>
 #include <QMouseEvent>
@@ -42,6 +43,26 @@
 }
 
 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()) {
@@ -49,7 +70,11 @@
                    this, SLOT(slotSceneRectChanged(const QRectF &)));
     }
     QGraphicsView::setScene(s);
-    if (scene()) fitInView(sceneRect(), Qt::IgnoreAspectRatio);
+    if (scene()) {
+        QRectF r = sceneRect();
+        DEBUG << "scene rect: " << r << ", my rect " << rect() << endl;
+        fit(r);
+    }
     m_cache = QPixmap();
     connect(scene(), SIGNAL(sceneRectChanged(const QRectF &)),
             this, SLOT(slotSceneRectChanged(const QRectF &)));
@@ -81,7 +106,7 @@
 void
 Panner::resizeEvent(QResizeEvent *)
 {
-    if (scene()) fitInView(sceneRect(), Qt::IgnoreAspectRatio);
+    if (scene()) fit(sceneRect());
     m_cache = QPixmap();
 }
 
@@ -89,7 +114,7 @@
 Panner::slotSceneRectChanged(const QRectF &newRect)
 {
     if (!scene()) return; // spurious
-    fitInView(newRect, Qt::IgnoreAspectRatio);
+    fit(newRect);
     m_cache = QPixmap();
     viewport()->update();
 }
@@ -137,7 +162,7 @@
 {
     if (m_cache.size() != viewport()->size()) {
 
-    std::cerr << "Panner: recreating cache" << std::endl;
+        DEBUG << "Panner: recreating cache" << endl;
 
         QGraphicsScene *s = scene();
         if (!s) return;
--- a/panner.h	Fri Nov 26 17:02:55 2010 +0000
+++ b/panner.h	Fri Nov 26 21:04:40 2010 +0000
@@ -51,6 +51,8 @@
 
     void moveTo(QPoint);
 
+    void fit(QRectF);
+
     virtual void paintEvent(QPaintEvent *);
     virtual void mousePressEvent(QMouseEvent *e);
     virtual void mouseMoveEvent(QMouseEvent *e);