Attached a patch which makes use of the new QProgress support in Systemcall.
It adds a menu entry in: View-">Show progress messages" which opens a dockwidget showing the messages from the external process. By default this widget is hidden, then instead a splashscreen pops up when lyx is busy and shows the last line of the actual process output. This is much faster than updating a QTextEdit in GuiProgress. I have to work around waitForFinished, because this function does not work if QApplication::processEvents() is called while the process is busy, on Windows it works. Is this maybe a Qt bug? Peter -- GMX FreeDSL mit DSL 6.000 Flatrate und Telefonanschluss nur 17,95 Euro/mtl.! http://dslspecial.gmx.de/freedsl-aktionspreis/?ac=OM.AD.PD003K11308T4569a
Index: src/frontends/qt4/GuiProgress.cpp =================================================================== --- src/frontends/qt4/GuiProgress.cpp (Revision 0) +++ src/frontends/qt4/GuiProgress.cpp (Revision 0) @@ -0,0 +1,165 @@ +// -*- C++ -*- +/** + * \file GuiProgress.cpp + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Peter KÃŒmmel + * + * Full author contact details are available in file CREDITS. + */ + +#include <config.h> + +#include "GuiProgress.h" + +#include "qt_helpers.h" + +#include "support/Systemcall.h" + +#include <QApplication> + + + +namespace lyx { +namespace frontend { + + +static void processEvents() +{ + // QEventLoop::ExcludeUserInputEvents: + // don't allow user inputs while processing a document + // if we allow it, we open will Pandora's Box of multithreading + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); +} + + +GuiProgress::GuiProgress(GuiView & parent, Qt::DockWidgetArea area, + Qt::WindowFlags flags) : DockView(parent, "progress", "External tools output", area, flags) +{ + setWindowTitle(qt_("External process monitoring")); + setWidget(&text_edit); + text_edit.setReadOnly(true); + support::Systemcall::registerProgressInterface(this); +} + + +void GuiProgress::processStarted() +{ + appendMessage("Process successfully started"); +} + + +void GuiProgress::processFinished() +{ +} + + +void GuiProgress::appendMessage(QString const & msg) +{ + text_edit.insertPlainText(msg); + text_edit.ensureCursorVisible(); + processEvents(); +} + + +void GuiProgress::appendError(QString const & msg) +{ + appendMessage(msg); +} + + +void GuiProgress::clearMessages() +{ + text_edit.clear(); + processEvents(); +} + + +void GuiProgress::showEvent(QShowEvent* event) +{ + // don't show splash when progress window is visible + support::Systemcall::registerProgressInterface(this); +} + + +void GuiProgress::hideEvent(QHideEvent* event) +{ + // show splash when no progress window is visible + support::Systemcall::registerProgressInterface(GuiProgressSplash::instance()); +} + + + +Dialog * createGuiProgress(GuiView & lv) +{ + GuiView & guiview = static_cast<GuiView &>(lv); +#ifdef Q_WS_MACX + // TODO where to show up on the Mac? + //return new GuiProgress(guiview, Qt::RightDockWidgetArea, Qt::Drawer); +#else + return new GuiProgress(guiview, Qt::BottomDockWidgetArea); +#endif +} + + + +GuiProgressSplash::GuiProgressSplash() +{ + splash.setPixmap(QPixmap(":/images/banner.png")); +} + + +void GuiProgressSplash::processStarted() +{ + splash.show(); + processEvents(); +} + + +void GuiProgressSplash::processFinished() +{ + splash.close(); +} + + +void GuiProgressSplash::appendMessage(QString const & msg) +{ + QStringList lines = msg.split("\n"); + while (lines.last().isEmpty() && !lines.isEmpty()) { + lines.pop_back(); + } + splash.showMessage(lines.last(), Qt::AlignLeft | Qt::AlignBottom); + processEvents(); +} + + +void GuiProgressSplash::appendError(QString const & msg) +{ + appendMessage(msg); +} + + +void GuiProgressSplash::clearMessages() +{ + splash.showMessage(QString(), Qt::AlignLeft | Qt::AlignBottom); + processEvents(); +} + +GuiProgressSplash* GuiProgressSplash::splash_instance = 0; + +void GuiProgressSplash::setInstance(GuiProgressSplash* splash) +{ + splash_instance = splash; +} + +GuiProgressSplash* GuiProgressSplash::instance() +{ + return splash_instance; +} + + +} // namespace frontend +} // namespace lyx + + Index: src/frontends/qt4/GuiView.cpp =================================================================== --- src/frontends/qt4/GuiView.cpp (Revision 29974) +++ src/frontends/qt4/GuiView.cpp (Arbeitskopie) @@ -27,6 +27,7 @@ #include "GuiToolbar.h" #include "Menus.h" #include "TocModel.h" +#include "GuiProgress.h" #include "qt_helpers.h" @@ -69,6 +70,7 @@ #include "support/os.h" #include "support/Package.h" #include "support/Timeout.h" +#include "support/Systemcall.h" #include <QAction> #include <QApplication> @@ -173,6 +175,10 @@ stack_widget_->addWidget(bg_widget_); stack_widget_->addWidget(splitter_); setBackground(); + + splash_ = new GuiProgressSplash; + GuiProgressSplash::setInstance(splash_); + support::Systemcall::registerProgressInterface(splash_); } ~GuiViewPrivate() @@ -180,6 +186,8 @@ delete splitter_; delete bg_widget_; delete stack_widget_; + GuiProgressSplash::setInstance(0); + delete splash_; } QMenu * toolBarPopup(GuiView * parent) @@ -284,6 +292,8 @@ /// TocModels toc_models_; + + GuiProgressSplash* splash_; }; @@ -1267,7 +1277,9 @@ enable = name == "aboutlyx" || name == "file" //FIXME: should be removed. || name == "prefs" - || name == "texinfo"; + || name == "texinfo" + || name == "texinfo" + || name == "progress"; else if (name == "print") enable = buf->isExportable("dvi") && lyxrc.print_command != "none"; @@ -2423,7 +2435,7 @@ "mathmatrix", "mathspace", "nomenclature", "nomencl_print", "note", "paragraph", "phantom", "prefs", "print", "ref", "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate", "thesaurus", "texinfo", -"toc", "view-source", "vspace", "wrap" }; +"toc", "view-source", "vspace", "wrap", "progress" }; char const * const * const end_dialognames = dialognames + (sizeof(dialognames) / sizeof(char *)); @@ -2630,6 +2642,7 @@ Dialog * createGuiVSpace(GuiView & lv); Dialog * createGuiViewSource(GuiView & lv); Dialog * createGuiWrap(GuiView & lv); +Dialog * createGuiProgress(GuiView & lv); Dialog * GuiView::build(string const & name) @@ -2732,6 +2745,8 @@ return createGuiVSpace(*this); if (name == "wrap") return createGuiWrap(*this); + if (name == "progress") + return createGuiProgress(*this); return 0; } Index: src/frontends/qt4/GuiProgress.h =================================================================== --- src/frontends/qt4/GuiProgress.h (Revision 0) +++ src/frontends/qt4/GuiProgress.h (Revision 0) @@ -0,0 +1,89 @@ +// -*- C++ -*- +/** + * \file GuiProgress.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Peter KÃŒmmel + * + * Full author contact details are available in file CREDITS. + */ + +#ifndef GUIPROGRESS_H +#define GUIPROGRESS_H + +#include "support/ProgressInterface.h" + +#include "DockView.h" + +#include <QTextEdit> +#include <QSplashScreen> + +#include <string> + + +namespace lyx { +namespace frontend { + + +class GuiProgress : + public DockView, + public lyx::support::ProgressInterface +{ + +public: + GuiProgress( + GuiView & parent, ///< the main window where to dock. + Qt::DockWidgetArea area, ///< Position of the dock (and also drawer) + Qt::WindowFlags flags = 0); + + void processStarted(); + void processFinished(); + void appendMessage(QString const &); + void appendError(QString const &); + void clearMessages(); + + /// Controller inherited method. + ///@{ + bool initialiseParams(std::string const & source) { return true; } + void clearParams() {} + void dispatchParams() {} + bool isBufferDependent() const { return false; } + bool canApply() const { return true; } + bool canApplyToReadOnly() const { return true; } + void updateView() {} + ///@} + + + void showEvent(QShowEvent* event); + void hideEvent(QHideEvent* event); + +private: + QTextEdit text_edit; +}; + + +class GuiProgressSplash : public lyx::support::ProgressInterface +{ +public: + GuiProgressSplash(); + + void processStarted(); + void processFinished(); + void appendMessage(QString const &); + void appendError(QString const &); + void clearMessages(); + + static GuiProgressSplash* instance(); + static void setInstance(GuiProgressSplash*); + +private: + QSplashScreen splash; + static GuiProgressSplash* splash_instance; +}; + +} // namespace frontend +} // namespace lyx + +#endif + Index: src/frontends/qt4/Makefile.am =================================================================== --- src/frontends/qt4/Makefile.am (Revision 29974) +++ src/frontends/qt4/Makefile.am (Arbeitskopie) @@ -107,6 +107,7 @@ GuiPrint.cpp \ GuiPrintindex.cpp \ GuiPrintNomencl.cpp \ + GuiProgress.cpp \ GuiRef.cpp \ GuiSearch.cpp \ GuiSelection.cpp \ @@ -205,6 +206,7 @@ GuiPrint.h \ GuiPrintindex.h \ GuiPrintNomencl.h \ + GuiProgress.h \ GuiRef.h \ GuiSearch.h \ GuiSelection.h \ Index: src/support/Systemcall.cpp =================================================================== --- src/support/Systemcall.cpp (Revision 29974) +++ src/support/Systemcall.cpp (Arbeitskopie) @@ -4,9 +4,9 @@ * Licence details can be found in the file COPYING. * * \author Asger Alstrup - * - * Interface cleaned up by * \author Angus Leeming + * \author Enrico Forestieri + * \author Peter KÃŒmmel * * Full author contact details are available in file CREDITS. */ @@ -19,11 +19,15 @@ #include "support/Systemcall.h" #include "support/SystemcallPrivate.h" #include "support/os.h" +#include "support/ProgressInterface.h" #include <cstdlib> #include <iostream> #include <QProcess> +#include <QApplication> +#include <QEventLoop> +#include <QTimer> #define USE_QPROCESS @@ -42,6 +46,38 @@ } +class ProgressDummy : public ProgressInterface +{ +public: + ProgressDummy() {} + + void processStarted() {} + void processFinished() {} + void appendMessage(QString const &) {} + void appendError(QString const &) {} + void clearMessages() {} +}; + + +static ProgressInterface* progress_impl = 0; + + +void Systemcall::registerProgressInterface(ProgressInterface* p) +{ + progress_impl = p; +} + +ProgressInterface* Systemcall::progress() +{ + if (!progress_impl) { + // TODO leaks + progress_impl = new ProgressDummy; + } + return progress_impl; +} + + + // Reuse of instance #ifndef USE_QPROCESS int Systemcall::startscript(Starttype how, string const & what) @@ -105,9 +141,17 @@ if (os::is_terminal(os::STDERR)) console.showerr(); + ProgressInterface* progress = Systemcall::progress(); + + progress->clearMessages(); + progress->appendMessage("Starting process:\n"); + progress->appendMessage(cmd + "\n"); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + process->start(cmd); if (!process->waitForStarted(3000)) { - LYXERR0("Qprocess " << cmd << " did not start!"); + LYXERR0("QProcess " << cmd << " did not start!"); LYXERR0("error " << process->error()); LYXERR0("state " << process->state()); LYXERR0("status " << process->exitStatus()); @@ -118,16 +162,26 @@ return 0; } - if (!process->waitForFinished(180000)) { - LYXERR0("Qprocess " << cmd << " did not finished!"); + // on Linux waitForFinished does not return when we call QApplication::processEvents() + QTimer timeout; + timeout.setSingleShot(true); + timeout.setInterval(180000); + QEventLoop loop; + QObject::connect(&timeout, SIGNAL(timeout()), &loop, SLOT(quit())); + QObject::connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit())); + timeout.start(); + loop.exec(); + if (!timeout.isActive()) { + LYXERR0("QProcess " << cmd << " did not finished!"); LYXERR0("error " << process->error()); LYXERR0("state " << process->state()); LYXERR0("status " << process->exitStatus()); return 20; } + int const exit_code = process->exitCode(); if (exit_code) { - LYXERR0("Qprocess " << cmd << " finished!"); + LYXERR0("QProcess " << cmd << " finished!"); LYXERR0("exitCode " << process->exitCode()); LYXERR0("error " << process->error()); LYXERR0("state " << process->state()); @@ -145,6 +199,7 @@ process->readAllStandardError().data())); killProcess(process); + return exit_code; } @@ -154,6 +209,11 @@ { connect(proc, SIGNAL(readyReadStandardOutput()), SLOT(stdOut())); connect(proc, SIGNAL(readyReadStandardError()), SLOT(stdErr())); + connect(proc, SIGNAL(error(QProcess::ProcessError)), + SLOT(processError(QProcess::ProcessError))); + connect(proc, SIGNAL(started()), this, SLOT(processStarted())); + connect(proc, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(processFinished(int, QProcess::ExitStatus))); } @@ -185,6 +245,7 @@ outdata_[outindex_] = '\0'; outindex_ = 0; cout << outdata_; + Systemcall::progress()->appendMessage(QString::fromLocal8Bit(outdata_)); } } } @@ -202,11 +263,71 @@ errdata_[errindex_] = '\0'; errindex_ = 0; cerr << errdata_; + Systemcall::progress()->appendError(QString::fromLocal8Bit(errdata_)); } } } } + +void ConOut::processError(QProcess::ProcessError err) +{ + QString message; + switch (err) { + case QProcess::FailedToStart: + message = "The process failed to start. Either the invoked program is missing, " + "or you may have insufficient permissions to invoke the program."; + break; + case QProcess::Crashed: + message = "The process crashed some time after starting successfully."; + break; + case QProcess::Timedout: + message = "The process timed out. It might be restarted automatically."; + break; + case QProcess::WriteError: + message = "An error occurred when attempting to write to the process-> For example, " + "the process may not be running, or it may have closed its input channel."; + break; + case QProcess::ReadError: + message = "An error occurred when attempting to read from the process-> For example, " + "the process may not be running."; + break; + case QProcess::UnknownError: + default: + message = "An unknown error occured."; + break; + } + Systemcall::progress()->appendMessage("\nThe process failed: " + message + '\n'); +} + + +void ConOut::processStarted() +{ + Systemcall::progress()->processStarted(); +} + + +void ConOut::processFinished(int, QProcess::ExitStatus status) +{ + ProgressInterface* progress = Systemcall::progress(); + + QString message; + switch (status) { + case QProcess::NormalExit: + message = "The process exited normally."; + break; + case QProcess::CrashExit: + message = "The process crashed."; + break; + default: + message = "Unknown exit state."; + break; + } + progress->appendMessage("Process finished: " + message + '\n'); + progress->processFinished(); +} + + #include "moc_SystemcallPrivate.cpp" #endif Index: src/support/Systemcall.h =================================================================== --- src/support/Systemcall.h (Revision 29974) +++ src/support/Systemcall.h (Arbeitskopie) @@ -20,6 +20,9 @@ namespace lyx { namespace support { +class ProgressInterface; + + /** * An instance of Class Systemcall represents a single child process. * @@ -43,6 +46,11 @@ * by spaces. */ int startscript(Starttype how, std::string const & what); + + static void registerProgressInterface(ProgressInterface*); + static ProgressInterface* progress(); + +private: }; } // namespace support Index: src/support/ProgressInterface.h =================================================================== --- src/support/ProgressInterface.h (Revision 0) +++ src/support/ProgressInterface.h (Revision 0) @@ -0,0 +1,41 @@ +// -*- C++ -*- +/** + * \file ProgressInterface.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Peter KÃŒmmel + * + * Full author contact details are available in file CREDITS. + */ + +#ifndef LYX_SUPPORT_PROGRESSINTERFACE_H +#define LYX_SUPPORT_PROGRESSINTERFACE_H + +class QString; + +namespace lyx { +namespace support { + + +class ProgressInterface +{ +public: + virtual ~ProgressInterface() {} + + virtual void processStarted() = 0; + virtual void processFinished() = 0; + virtual void appendMessage(QString const &) = 0; + virtual void appendError(QString const &) = 0; + virtual void clearMessages() = 0; + +protected: + ProgressInterface() {} +}; + + +} // namespace support +} // namespace lyx + +#endif // LYX_SUPPORT_PROGRESSINTERFACE_H + Index: src/support/SystemcallPrivate.h =================================================================== --- src/support/SystemcallPrivate.h (Revision 29974) +++ src/support/SystemcallPrivate.h (Arbeitskopie) @@ -14,7 +14,7 @@ #include <QObject> -class QProcess; +#include <QProcess> namespace lyx { namespace support { @@ -58,6 +58,9 @@ public Q_SLOTS: void stdOut(); void stdErr(); + void processError(QProcess::ProcessError); + void processStarted(); + void processFinished(int, QProcess::ExitStatus status); }; } // namespace support Index: src/support/Makefile.am =================================================================== --- src/support/Makefile.am (Revision 29974) +++ src/support/Makefile.am (Arbeitskopie) @@ -77,6 +77,7 @@ Path.h \ Package.cpp \ Package.h \ + ProgressInterface.h \ qstring_helpers.cpp \ qstring_helpers.h \ socktools.cpp \ Index: lib/ui/stdmenus.inc =================================================================== --- lib/ui/stdmenus.inc (Revision 29974) +++ lib/ui/stdmenus.inc (Arbeitskopie) @@ -310,6 +310,7 @@ Item "Split View Into Upper And Lower Half|e" "split-view vertical" Item "Close Tab Group|G" "close-tab-group" Item "Fullscreen|l" "ui-toggle fullscreen" + Item "Show process messages" "dialog-toggle progress" Submenu "Toolbars|b" "toolbars" Separator Documents