I am working on building a GUI around a console application. I would like to be able to click a button to run the console app and show the console output inside of the GUI itself. How might I accomplish this? I am working in Linux.
You could also try QProcess. It provides a Qt interface to launching external processes, reading their I/O and waiting, or not, on their completion.
For your purpose, it sounds like you want the process to run asynchronously, so code might look like :
myprocessstarter.h :
#include <QObject>
#include <QProcess>
#include <QDebug>
class MyProcessStarter : public QObject
{
Q_OBJECT
public:
MyProcessStarter() : QObject() {};
void StartProcess();
private slots:
void readStandardOutput();
private:
QProcess *myProcess;
};
main.cpp:
#include "myprocessstarter.h"
void MyProcessStarter::StartProcess()
{
QString program = "dir";
QStringList arguments;
// Add any arguments you want to be passed
myProcess = new QProcess(this);
connect(myProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readStandardOutput()));
myProcess->start(program, arguments);
}
void MyProcessStarter::readStandardOutput()
{
QByteArray processOutput;
processOutput = myProcess->readAllStandardOutput();
qDebug() << "Output was " << QString(processOutput);
}
void main(int argc, char** argv)
{
MyProcessStarter s;
s.StartProcess();
}
I wanted to do something similar in one of my applications. I redirected all output from the standard stream (cout) to my console window. To periodically read out the stream contents I use a timer loop. Works fine for me.
StdRedirector.cpp
#include "StdRedirector.h"
QMutex coutMutex;
void outcallback(const char* ptr, std::streamsize count, void* bufferString)
{
string *b = (string *) bufferString;
string t;
for (int i=0; i < count; i++)
{
if (ptr[i] == '\n')
{
t = t + "\n";
} else {
t = t + ptr[i];
}
}
coutMutex.lock();
*b = *b + t;
coutMutex.unlock();
}
void ConsoleWindow::updateTimer(void)
{
coutMutex.lock();
if (bufferString.size() > 0)
{
consoleBox->insertPlainText(QString(bufferString.c_str()));
bufferString.clear();
QScrollBar *sb = consoleBox->verticalScrollBar();
sb->setValue(sb->maximum());
}
coutMutex.unlock();
}
ConsoleWindow::ConsoleWindow(QWidget *parent) : QWidget(parent)
{
consoleBox = new QTextEdit(this);
consoleBox->setReadOnly(true);
stdRedirector = new StdRedirector<>(std::cout, outcallback, &bufferString);
QVBoxLayout *vb = new QVBoxLayout();
vb->addWidget(consoleBox);
vb->setMargin(0);
vb->setSpacing(0);
setLayout(vb);
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(updateTimer()));
timer->start(100);
}
ConsoleWindow::~ConsoleWindow()
{
delete stdRedirector;
}
StdRedirector.h
#ifndef STD_REDIRECTOR
#define STD_REDIRECTOR
#include <QWidget>
#include <QTextEdit>
#include <QString>
#include <QVBoxLayout>
#include <QTimer.h>
#include <QMutex>
#include <QScrollBar>
#include <iostream>
#include <string>
using namespace std;
template<class Elem = char, class Tr = std::char_traits<Elem>>
class StdRedirector : public std::basic_streambuf<Elem, Tr>
{
typedef void (*pfncb) ( const Elem*, std::streamsize _Count, void* pUsrData );
public:
StdRedirector(std::ostream& a_Stream, pfncb a_Cb, void* a_pUsrData) :
m_Stream(a_Stream),
m_pCbFunc(a_Cb),
m_pUserData(a_pUsrData)
{
m_pBuf = m_Stream.rdbuf(this);
}
~StdRedirector()
{
m_Stream.rdbuf(m_pBuf);
}
std::streamsize xsputn(const Elem* _Ptr, std::streamsize _Count)
{
m_pCbFunc(_Ptr, _Count, m_pUserData);
return _Count;
}
typename Tr::int_type overflow(typename Tr::int_type v)
{
Elem ch = Tr::to_char_type(v);
m_pCbFunc(&ch, 1, m_pUserData);
return Tr::not_eof(v);
}
protected:
std::basic_ostream<Elem, Tr>& m_Stream;
std::streambuf* m_pBuf;
pfncb m_pCbFunc;
void* m_pUserData;
};
class ConsoleWindow : public QWidget
{
Q_OBJECT
public:
ConsoleWindow(QWidget *parent = 0);
~ConsoleWindow();
public slots:
void updateTimer(void);
public:
QTextEdit *consoleBox;
StdRedirector<> *stdRedirector;
string bufferString;
};
#endif
The StdRedirector class is based on code from this forum post: http://www.qtforum.org/article/24554/displaying-std-cout-in-a-text-box.html
Take a look at the popen() function, it might do what you need.
Then you could pass the FILE * to a QTextStream and work in Qt style with it.
I suggest, rather than showing stdout in GUI, having own console output, which essentially means all messages you want to show to users you are sending to your own output.
This way you can have debug messages and such still available from console, wtih potential errors with connections and whatever that can happen and have fully controlled console output in GUI application. Of course this output can also be outputted to stdout so it is visible in console, but it also allows you to append a prefixs like WARNING LOG NOTICE NO_THIS_WENT_WRONG or whatever you want to show to users as your console entry.
Related
I'm trying to do a simple GET request (with modified User-Agent), return response to QML and do a JSON parsing.
Actually it only returns page content when loading is complete but it doesn't return it to QML.
Sorry for the noob question. I'm new to this language and I'm trying to learn it :)
Here's my code:
Home.qml
function getRequest() {
[...]
console.log('Request...')
var jsonResult = JSON.parse(connectNet.connectUrl("http://myURL.com/index.php").toString())
lbOutput.text = jsonResult.predictions[0].description.toString()
}
}
connectnet.cpp
#include "connectnet.h"
#include "stdio.h"
#include <QDebug>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QUrl>
connectNet::connectNet(QObject *parent) : QObject(parent)
{
}
void connectNet::connectUrl(QString url)
{
QNetworkAccessManager *manager = new QNetworkAccessManager();
QNetworkRequest request;
QNetworkReply *reply = NULL;
request.setUrl(QUrl(url));
request.setRawHeader( "User-Agent" , "FAKE USER AGENT HERE" );
reply = manager->get(request);
connect(manager, SIGNAL(finished(QNetworkReply*)), this,
SLOT(replyFinished(QNetworkReply*)));
}
QString connectNet::replyFinished(QNetworkReply *reply)
{
return reply->readAll();
}
appname.cpp
#ifdef QT_QML_DEBUG
#include <QtQuick>
#endif
#include <sailfishapp.h>
#include "connectnet.h"
int main(int argc, char *argv[])
{
//INIT SETTINGS
QGuiApplication *app = SailfishApp::application(argc, argv);
QQuickView *view = SailfishApp::createView();
connectNet ConnectNet;
view->rootContext()->setContextProperty("connectNet", &ConnectNet);
view->setSource(SailfishApp::pathTo("qml/APPNAME.qml"));
view->showFullScreen();
app->exec();
}
Hope I've well explained what I'm looking for. Thanks for your help.
====================================================
EDIT 20/08/2015: added updated connectnet.h
#ifndef CONNECTNET_H
#define CONNECTNET_H
#include <QObject>
#include <QNetworkReply>
#include <QDebug>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QUrl>
class ConnectNet : public QObject
{
Q_OBJECT
QNetworkAccessManager m_manager;
public:
ConnectNet(QObject * parent = 0) : QObject(parent) {
connect(&m_manager, &QNetworkAccessManager::finished,
[this](QNetworkReply * reply) {
if (reply->error() == QNetworkReply::NoError)
emit replyAvailable(QString::fromUtf8(reply->readAll()));
});
}
signals:
void replyAvailable(const QString & reply);
public slots:
void sendRequest(const QString url) {
QNetworkRequest request;
request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", "MyLittleAgent");
m_manager.get(request);
}
};
#endif // CONNECTNET_H
this part of code gives a lot of errors :( (screenshot below)
connect(&m_manager, &QNetworkAccessManager::finished,
[this](QNetworkReply * reply) {
if (reply->error() == QNetworkReply::NoError)
emit replyAvailable(QString::fromUtf8(reply->readAll()));
});
compiling erros: http://i.stack.imgur.com/30vWn.jpg
Your problem is that you think synchronously. The connectUrl cannot return a value (and it doesn't), since when it runs the result is not available. What you must do, instead, is for the ConnectNet class to emit a signal when the data is available.
It'd be a horrible idea if you tried to make a synchronous wrapper that did return the value: the QML engine would be stuck as long as it took for the result to be received. You could freeze your application by pulling the network cable at the right moment, or if the server was down. Users hate that, and it's a horrible antipattern that must be expediently eliminated and discouraged.
Here's how your ConnectNet (please, not connectNet, lowercase names are for members!) class could look. Note that the QNetworkAccessManager instance doesn't need to be a pointer.
class ConnectNet : public QObject {
Q_OBJECT
QNetworkAccessManager m_manager;
public:
ConnectNet(QObject * parent = 0) : QObject(parent) {
connect(&m_manager, &QNetworkAccessManager::finished,
[this](QNetworkReply * reply) {
if (reply->error() == QNetworkReply::NoError)
emit replyAvailable(QString::fromUtf8(reply->readAll()));
});
}
Q_SLOT void sendRequest(const QString & url) {
auto request = QNetworkRequest(QUrl(url));
request.setRawHeader("User-Agent", "MyLittleAgent");
m_manager.get(request);
}
Q_SIGNAL void replyAvailable(const QString & reply);
};
Since connectNet instance instance is exposed as a property in the global QML context, you can connect to its signals as follows:
function getRequest() {
connectNet.sendRequest("http://myURL.com/index.php")
}
function resultHandler(result) {
var jsonResult = JSON.parse(result.toString())
lbOutput.text = jsonResult.predictions[0].description.toString()
}
Rectangle { // or any other item
Component.onCompleted: {
connectNet.replyAvailable.connect(resultHandler)
}
...
}
I am saving an image of a QQuickWidget with several QML children but all I have is a blank image.
C++ side:
QQuickWidget* content..
content->setSource(QUrl("qml:/main.qml"));
QPixmap *pm = content->grab(QRect(QPoint(0,0),QSize(-1,-1));
pm->save("someFilename.png", 0, 100);
QML side:
Rectangle{ width: 5; height: 5; color: "yellow"; objectname: "rootobj"}
In the QML I wish to dynamically add children and be able to show them in the image. I have tried QQuickWindow grabWindow method with a connection to a slot and it works but it captures only the window visible area and I need to capture the whole QML.
I believe this is not rocket science just that I am not getting it somewhere. Thanks for your replies!
Addendum:
Ok, I do not think its the issue of before/after rendering since I can see all the qml children before I call the picture grabber. So sorry for not being precise.
c++ side:
QQuickWidget* content..
content->setSource(QUrl("qml:/main.qml"));
//do all my dynamic qml children adding
After I can visually see all my qml:
QPixmap *pm = content->grab(QRect(QPoint(0,0),QSize(-1,-1));
pm->save(....
Unless I am wrong, I dont think its rendering issue. Thank you!
Issue is like Mido said. You can solve it like follows.
Create a class Viewer:
viewer.h
class Viewer : public QQuickView{
Q_OBJECT
public:
explicit Viewer(QWindow *parent = 0);
Viewer(bool showBar);
virtual ~Viewer();
void setMainQmlFile(const QString file);
void addImportPath(const QString path);
public slots:
void beforeRendering();
void afterRendering()
}
Viewer.cpp
#include "viewer.h"
Viewer::Viewer(QWindow *parent)
: QQuickView(parent)
{
setWidth(800);
setHeight(480);
connect(this, SIGNAL(beforeRendering()), this, SLOT(beforeRendering()));
connect(this, SIGNAL(afterRendering()), this, SLOT(afterRendering()));
}
void Viewer::setMainQmlFile(const QString file)
{
setSource(QUrl::fromLocalFile(file));
}
void Viewer::addImportPath(const QString path)
{
engine()->addImportPath(path);
}
void Viewer::beforeRendering()
{
//
}
void Viewer::afterRendering()
{
//grab window
QImage img = this->grabWindow();
img.save(path);
//or your code
}
main.cpp
Viewer *viewer = new Viewer;
//
///
//
viewer->setMainQmlFile(QStringLiteral("qml/main.qml"));
viewer->show();
I think your issue is that the capture screen is done before the rendering of the QML object.
In order to make it work you should connect the grab of the signal after rendering signal:
connect(this, SIGNAL(beforeRendering()), this, SLOT(sltBeforeRendering()));
connect(this, SIGNAL(afterRendering()), this, SLOT(sltAfterRendering()));
do the grab in sltAfterRendering slot.
To grab screen I use the grabWindow() function and I call it from QML.
It depends on the behaviour that you want from your software.
Try this:
grabber.h
#ifndef GRABBER_H
#define GRABBER_H
#include <QObject>
#include <QImage>
#include <QQuickView>
class Grabber : public QObject
{
Q_OBJECT
public:
explicit Grabber(QObject *parent = 0);
Grabber(QQuickView *view);
~Grabber();
Q_INVOKABLE void capture(QString const &path) const;
signals:
public slots:
private:
QQuickView* view_;
};
#endif // GRABBER_H
grabber.cpp
#include "grabber.h"
Grabber::Grabber(QObject *parent) :
QObject(parent)
{
}
Grabber::Grabber(QQuickView* view) :
view_(view)
{
}
Grabber::~Grabber()
{
if(view_ != NULL)
{
delete view_;
view_ = NULL;
}
}
void Grabber::capture(QString const &path) const
{
QImage img = view_->grabWindow();
img.save(path);
}
main.cpp
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include <QQmlContext>
#include <QQmlEngine>
#include "grabber.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer *viewer = new QtQuick2ApplicationViewer;
Grabber * grab = new Grabber(viewer);
viewer->setHeight(480);
viewer->setWidth(800);
viewer->rootContext()->setContextProperty("grab", grab);
viewer->setMainQmlFile(QStringLiteral("qml/main.qml"));
viewer->showExpanded();
return app.exec();
}
Call it from QML with:
grab.capture(path + "imageName.png")
I can't see what I'm doing wrong. I'm trying to create some properties in a class (as I did before) but this time I'm getting the error "class Foo has no member named MyProp"
The header is:
#ifndef P_H
#define P_H
#include <QObject>
class P : public QObject
{
Q_OBJECT
Q_PROPERTY(int Prop READ getProp WRITE setProp)
public:
explicit P(QObject *parent = 0);
int getProp() const;
void setProp(int nP);
private:
int m_p;
};
#endif // P_H
and the cpp file is:
#include "p.h"
P::P(QObject *parent) :
QObject(parent)
{
}
int P::getProp() const
{
return m_p;
}
void P::setProp(int nP)
{
m_p = nP;
}
But when I try to use foobar.P I got the error class P has no member named P. I've been reading Qt documentation and I can't see any difference. Does anyone see what I'm doing wrong?
I'm using Qt Creator 2.4.1 and Qt 4.8.
[... Edit ...]
Here is how I'm trying to use it:
#include "p.h"
int main(int argc, char *argv[])
{
P c;
c.Prop = 2;
return 0;
}
This is the simplest example I could think of and I got the same error.
Thanks in advance.
You need to use it like this:
P c;
c.setProperty("Prop", 42); // set the property
c.property("Prop"); // retrieve the property
I tried declaring a signal in a prototype and then connecting it is script funcition for some reason it does not work as I hoped. My code is as follows. Could some one help me in this.
What I expected was, once I called p.setText('New String') in the script code, since setText emits the textChanged signal it should invoke the slot which is catchSignal(text) already connected in the script code.
Prototype header
#ifndef SCRIPTACTION_H
#define SCRIPTACTION_H
#include <QObject>
#include <QtScript>
class ScriptAction : public QObject , public QScriptable
{
Q_OBJECT
public:
ScriptAction(QObject *parent = 0);
signals:
void textChanged(const QString changedString);
};
#endif // SCRIPTACTION_H
Class
#include "scriptaction.h"
#include <QAction>
Q_DECLARE_METATYPE(QAction*)
ScriptAction::ScriptAction(QObject *parent) : QObject(parent)
{
}
Main Class
#include <QApplication>
#include <QDebug>
#include <QAction>
#include "scriptaction.h"
#include <QPushButton>
Q_DECLARE_METATYPE(QAction*)
QScriptValue qAction_Constructor(QScriptContext *ctx, QScriptEngine *eng)
{
qDebug() << "QAction is called";
if(ctx->isCalledAsConstructor())
{
QObject *parent = ctx->argument(0).toQObject();
QAction *action = new QAction("Test",parent);
return eng->newQObject(action, QScriptEngine::ScriptOwnership);
} else {
return QString("invalid call. Use new Constructor");
}
}
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
QScriptEngine engine;
//Evaluating a simaple expresssion
qDebug() << engine.evaluate("1+2").toNumber();
QPushButton button;
QScriptValue buttonScript= engine.newQObject(&button);
engine.globalObject().setProperty("button", buttonScript);
engine.evaluate("button.text ='Hello Text'; button.show()");
//QAction Prototype
ScriptAction qsAction ;
QScriptValue script_proto = engine.newQObject(&qsAction);
engine.setDefaultPrototype(qMetaTypeId<QAction*>(), script_proto);
QScriptValue ctor = engine.newFunction(qAction_Constructor , script_proto);
QScriptValue metaObject = engine.newQMetaObject(&QObject::staticMetaObject, ctor);
engine.globalObject().setProperty("QSAction" , metaObject);
engine.evaluate("var p = new QSAction(button);p.textChanged.connect(catchSignal);");
engine.evaluate("function catchSignal(text) { print ('PROTOTYPE SIGNAL IS CALLED ',text); } p.setText('New String'); " );
return app.exec();
}
I got rid of the issue, and now I see the signal is being triggered and slot is called properly.
All I did was moving the code to a separate script file and start using the QScriptDebugger to see its output. Then I figured there was an error and the code is edited to work.
Anyone who wants an example prototype class, this will hopefully be a good guideline.
I am trying to display and get the result a message box from outside of a QObject class. I seem to be able to generate the dialog like this:
#include <iostream>
#include <QApplication>
#include <QtConcurrentRun>
#include <QMessageBox>
class DialogHandler : public QObject
{
Q_OBJECT
signals:
void MySignal();
public:
DialogHandler()
{
connect( this, SIGNAL( MySignal() ), this, SLOT(MySlot()) );
}
void EmitSignal()
{
emit MySignal();
}
public slots:
void MySlot()
{
QMessageBox* dialog = new QMessageBox;
dialog->setText("Test Text");
dialog->exec();
int result = dialog->result();
if(result)
{
std::cout << "ok" << std::endl;
}
else
{
std::cout << "invalid" << std::endl;
}
}
};
#include "main.moc" // For CMake's automoc
void MyFunction(DialogHandler* dialogHandler)
{
dialogHandler->EmitSignal();
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
DialogHandler* dialogHandler = new DialogHandler;
MyFunction(dialogHandler);
return app.exec();
}
To get the result back in MyFunction, it seems to work to do simply pass an object to fill with the result like this:
#include <iostream>
#include <QApplication>
#include <QtConcurrentRun>
#include <QMessageBox>
class DialogHandler : public QObject
{
Q_OBJECT
signals:
void MySignal(int* returnValue);
public:
DialogHandler()
{
connect( this, SIGNAL( MySignal(int*) ), this, SLOT(MySlot(int*)), Qt::BlockingQueuedConnection );
}
void EmitSignal(int* returnValue)
{
emit MySignal(returnValue);
}
public slots:
void MySlot(int* returnValue)
{
std::cout << "input: " << *returnValue << std::endl;
QMessageBox* dialog = new QMessageBox;
dialog->addButton(QMessageBox::Yes);
dialog->addButton(QMessageBox::No);
dialog->setText("Test Text");
dialog->exec();
int result = dialog->result();
if(result == QMessageBox::Yes)
{
*returnValue = 1;
}
else
{
*returnValue = 0;
}
}
};
#include "main.moc" // For CMake's automoc
void MyFunction(DialogHandler* dialogHandler)
{
int returnValue = -1;
dialogHandler->EmitSignal(&returnValue);
std::cout << "returnValue: " << returnValue << std::endl;
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
DialogHandler* dialogHandler = new DialogHandler;
QtConcurrent::run(MyFunction, dialogHandler);
std::cout << "End" << std::endl;
return app.exec();
}
Does that seem reasonable? Is there a better way to do it?
This isn't possible quite like you have it, but with a bit of work it could be done. One option, of course, would be to convert your class to a QObject, at which point you could send signals. It doesn't help for the delay during exec, however. If that is necessary, you could have a messaging class that lives in the main UI thread, but can be called from other threads. The function called from other threads would need to lock, make a semaphore, and send an event to itself with the semaphore and message to be displayed. Then, in customEvent (which would be in the UI thread), you would create the message box, exec it, and trigger the semaphore after the message box is cleared.
Of course, things get a bit more complicated if you need to send information back the other way as well. Then you'll need a complete subsystem for your program, instead of just one basic class like I describe here.