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

Reply via email to