I'm working with QtWebKit where I'm displaying dynamically created HTML content, and I need to display the images that I retrieve from the database. For example, if I need to display an image from the resource, I add this line to the content I use in QWebView::setHtml:
<img src="qrc:/images/image.jpg"/>
That works great, WebView automatically finds the resource and displays it. Now I need to substitute this image with the content that I retrieve from the database, and that doesn't have a file in the filesystem. How to do that?
Can I manage the qrc namespace adding the content dynamically? Is that possible to add my own QRC handler that would receive and serve the requests from WebView? If not QRC, is there any other protocol that I can use to provide the content to the images in WebView?
I have full control over what I'm adding to the WebView using the setHtml method.
Update: I would like to solve the same problem for QWebEngineView as well.
QtWebkit
In the case of QtWebkit you must intercept the request using a QNetworkAccessManager and resend a custom QNetworkReply.
#include <QtWebKitWidgets>
class CustomReply : public QNetworkReply{
public:
explicit CustomReply(const QByteArray & content, const QByteArray & contentType, const QUrl & url):
QNetworkReply(), m_content(content){
offset = 0;
setUrl(url);
open(ReadOnly | Unbuffered);
setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType));
setHeader(QNetworkRequest::ContentLengthHeader, QVariant(m_content.size()));
QTimer::singleShot(0, this, &CustomReply::dispatch);
}
bool isSequential() const{
return true;
}
qint64 bytesAvailable() const{
return m_content.size() - offset + QIODevice::bytesAvailable();
}
public slots:
void abort(){
}
protected:
qint64 readData(char *data, qint64 maxSize){
if (offset < m_content.size()) {
qint64 number = qMin(maxSize, m_content.size() - offset);
::memcpy(data, m_content.constData() + offset, number);
offset += number;
return number;
} else
return -1;
}
private:
void dispatch(){
emit readyRead();
emit finished();
}
QByteArray m_content;
qint64 offset;
};
class NetworkAccessManager: public QNetworkAccessManager{
public:
using QNetworkAccessManager::QNetworkAccessManager;
protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData){
qDebug() << request.url();
if (request.url() == QUrl("qrc:/images/image.jpg")){
QImage image(150, 150, QImage::Format_RGB32);
image.fill(QColor("salmon"));
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "JPEG");
return new CustomReply(ba, "image/jpeg", request.url());
}
return QNetworkAccessManager::createRequest(op, request, outgoingData);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWebView view;
view.resize(640, 480);
view.show();
view.page()->setNetworkAccessManager(new NetworkAccessManager);
QString html = R"(<img src="qrc:/images/image.jpg">)";
view.setHtml(html);
return a.exec();
}
QtWebEngine
In QtWebEngine you must implement a QWebEngineUrlSchemeHandler but you cannot use the qrc, http or https schemas:
#include <QtWebEngineWidgets>
#define SCHEMENAME "so"
class Handler : public QWebEngineUrlSchemeHandler{
public:
void requestStarted(QWebEngineUrlRequestJob *job){
if(job->requestUrl() == QUrl("so:/images/image.jpg")){
QImage image(150, 150, QImage::Format_RGB32);
image.fill(QColor("salmon"));
QBuffer *buffer = new QBuffer;
buffer->open(QIODevice::WriteOnly);
image.save(buffer, "JPEG");
buffer->seek(0);
buffer->close();
job->reply("image/jpeg", buffer);
}
}
static void registerUrlScheme(){
QWebEngineUrlScheme webUiScheme(SCHEMENAME);
webUiScheme.setFlags(QWebEngineUrlScheme::SecureScheme |
QWebEngineUrlScheme::LocalScheme |
QWebEngineUrlScheme::LocalAccessAllowed);
QWebEngineUrlScheme::registerScheme(webUiScheme);
}
};
int main(int argc, char *argv[])
{
Handler::registerUrlScheme();
QApplication a(argc, argv);
QWebEngineView view;
Handler handler;
view.page()->profile()->installUrlSchemeHandler(SCHEMENAME, &handler);
view.resize(640, 480);
view.show();
QString html = R"(<img src="so:/images/image.jpg">)";
view.setHtml(html);
return a.exec();
}
Related
I have read the QIODevice doc, but still don't know how to archive that.
What I want to do is to create a KeyBoard class deriving from QIODevice. which opens /dev/input/eventX. I hope that my code can use the readyRead() signal of KeyBoard. (QFile does not emit readyRead() signal)
class KeyBoard : public QIODevice {
public:
KeyBoard();
~KeyBoard();
protected:
qint64 readData(char *data, qint64 size);
qint64 writeData(const char *data, qint64 size);
};
What do I need to do in readData() and writeData()?
And how does my code use this class? (I just use the QCoreApplication, no gui)
Use QSocketNotifier on the open file handle. You can read from the device using QFile,or abuse QSerialPort, i.e. QSerialPort m_port{"input/eventX"}. See this answer for an example of using QSocketNotifier with stdin; /dev/input/eventX requires a similar approach.
Here's an example that works on /dev/stdio, but would work identically on /dev/input/eventX.
// https://github.com/KubaO/stackoverflown/tree/master/questions/dev-notifier-49402735
#include <QtCore>
#include <fcntl.h>
#include <boost/optional.hpp>
class DeviceFile : public QFile {
Q_OBJECT
boost::optional<QSocketNotifier> m_notifier;
public:
DeviceFile() {}
DeviceFile(const QString &name) : QFile(name) {}
DeviceFile(QObject * parent = {}) : QFile(parent) {}
DeviceFile(const QString &name, QObject *parent) : QFile(name, parent) {}
bool open(OpenMode flags) override {
return
QFile::isOpen()
|| QFile::open(flags)
&& fcntl(handle(), F_SETFL, O_NONBLOCK) != -1
&& (m_notifier.emplace(this->handle(), QSocketNotifier::Read, this), true)
&& m_notifier->isEnabled()
&& connect(&*m_notifier, &QSocketNotifier::activated, this, &QIODevice::readyRead)
|| (close(), false);
}
void close() override {
m_notifier.reset();
QFile::close();
}
};
int main(int argc, char **argv) {
QCoreApplication app{argc, argv};
DeviceFile dev("/dev/stdin");
QObject::connect(&dev, &QIODevice::readyRead, [&]{
qDebug() << "*";
if (dev.readAll().contains('q'))
app.quit();
});
if (dev.open(QIODevice::ReadOnly))
return app.exec();
}
#include "main.moc"
When I try to downloading file up to 50mb example, no problem, but with a big files give the following error
void MainWindow::downloadFile() {
QNetworkRequest requests;
requests.setUrl(QUrl("https://urlToFile"));
QSslConfiguration configSsl = QSslConfiguration::defaultConfiguration();
configSsl.setProtocol(QSsl::AnyProtocol);
requests.setSslConfiguration(configSsl);
QNetworkAccessManager *manager5 = new QNetworkAccessManager(this);
QNetworkReply *reply5;
reply5 = manager5->get( requests );
connect(manager5, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
}
void MainWindow::downloadFinished(QNetworkReply *data) {
QFile localFile("fileName");
if (!localFile.open(QIODevice::WriteOnly))
return;
localFile.write(data->readAll());
localFile.close();
}
In your code, You are holding the whole file in memory until the download process finishes (that is when QNetworkAccessManager::finished() signal gets emitted). Of course, this is not the best way to deal with large files.
Remember that QNetworkReply is a QIODevice. This means that you should use the readyRead() signal to save data chunks to the disk as soon as they are received from the network, in order to avoid holding the whole file in memory until the download is finished.
Here is a minimal complete example:
#include <QtNetwork>
int main(int argc, char* argv[]){
QCoreApplication a(argc, argv);
QNetworkAccessManager nam;
QFile file("downloadedFile.xxx");
if(!file.open(QIODevice::ReadWrite)) return 1;
QNetworkRequest request(QUrl("http://download_url/..."));
QNetworkReply* reply = nam.get(request);
QObject::connect(reply, &QNetworkReply::readyRead, [&]{
//this will be called every time a chunk of data is received
QByteArray data= reply->readAll();
qDebug() << "received data of size: " << data.size();
file.write(data);
});
//use the finished signal from the reply object to close the file
//and delete the reply object
QObject::connect(reply, &QNetworkReply::finished, [&]{
qDebug() << "finished downloading";
QByteArray data= reply->readAll();
file.write(data);
file.close();
reply->deleteLater();
a.quit();
});
return a.exec();
}
Update:
I have wrapped the whole thing in a class FileDownloader, which can be used to download a file using QNetworkAccessManager, Here is an example of using this class:
#include <QtWidgets>
#include <QtNetwork>
//downloads one file at a time, using a supplied QNetworkAccessManager object
class FileDownloader : public QObject{
Q_OBJECT
public:
explicit FileDownloader(QNetworkAccessManager* nam, QObject* parent= nullptr)
:QObject(parent),nam(nam)
{
}
~FileDownloader(){
//destructor cancels the ongoing dowload (if any)
if(networkReply){
a_abortDownload();
}
}
//call this function to start downloading a file from url to fileName
void startDownload(QUrl url, QString fileName){
if(networkReply) return;
destinationFile.setFileName(fileName);
if(!destinationFile.open(QIODevice::WriteOnly)) return;
emit goingBusy();
QNetworkRequest request(url);
networkReply= nam->get(request);
connect(networkReply, &QIODevice::readyRead, this, &FileDownloader::readData);
connect(networkReply, &QNetworkReply::downloadProgress,
this, &FileDownloader::downloadProgress);
connect(networkReply, &QNetworkReply::finished,
this, &FileDownloader::finishDownload);
}
//call this function to abort the ongoing download (if any)
void abortDownload(){
if(!networkReply) return;
a_abortDownload();
emit backReady();
}
//connect to the following signals to get information about the ongoing download
Q_SIGNAL void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
Q_SIGNAL void downloadSuccessful();
Q_SIGNAL void downloadError(QString errorString);
//the next two signals are used to indicate transitions between busy and
//ready states of the file downloader, they can be used to update the GUI
Q_SIGNAL void goingBusy();
Q_SIGNAL void backReady();
private:
Q_SLOT void readData(){
QByteArray data= networkReply->readAll();
destinationFile.write(data);
}
Q_SLOT void finishDownload(){
if(networkReply->error() != QNetworkReply::NoError){
//failed download
a_abortDownload();
emit downloadError(networkReply->errorString());
} else {
//successful download
QByteArray data= networkReply->readAll();
destinationFile.write(data);
destinationFile.close();
networkReply->deleteLater();
emit downloadSuccessful();
}
emit backReady();
}
//private function, cleans things up when the download is aborted
//(due to an error or user interaction)
void a_abortDownload(){
networkReply->abort();
networkReply->deleteLater();
destinationFile.close();
destinationFile.remove();
}
QNetworkAccessManager* nam;
QUrl downloadUrl;
QFile destinationFile;
QPointer<QNetworkReply> networkReply;
};
//A sample GUI application that uses the above class
class Widget : public QWidget{
Q_OBJECT
public:
explicit Widget(QWidget* parent= nullptr):QWidget(parent){
layout.addWidget(&lineEditUrl, 0, 0);
layout.addWidget(&buttonDownload, 0, 1);
layout.addWidget(&progressBar, 1, 0);
layout.addWidget(&buttonAbort, 1, 1);
layout.addWidget(&labelStatus, 2, 0, 1, 2);
lineEditUrl.setPlaceholderText("URL to download");
connect(&fileDownloader, &FileDownloader::downloadSuccessful,
this, &Widget::downloadFinished);
connect(&fileDownloader, &FileDownloader::downloadError,
this, &Widget::error);
connect(&fileDownloader, &FileDownloader::downloadProgress,
this, &Widget::updateProgress);
connect(&buttonDownload, &QPushButton::clicked,
this, &Widget::startDownload);
connect(&buttonAbort, &QPushButton::clicked,
this, &Widget::abortDownload);
showReady();
//use goingBusy() and backReady() from FileDownloader signals to update the GUI
connect(&fileDownloader, &FileDownloader::goingBusy, this, &Widget::showBusy);
connect(&fileDownloader, &FileDownloader::backReady, this, &Widget::showReady);
}
~Widget() = default;
Q_SLOT void startDownload(){
if(lineEditUrl.text().isEmpty()){
QMessageBox::critical(this, "Error", "Enter file Url", QMessageBox::Ok);
return;
}
QString fileName =
QFileDialog::getSaveFileName(this, "Destination File");
if(fileName.isEmpty()) return;
QUrl url= lineEditUrl.text();
fileDownloader.startDownload(url, fileName);
}
Q_SLOT void abortDownload(){
fileDownloader.abortDownload();
}
Q_SLOT void downloadFinished(){
labelStatus.setText("Download finished successfully");
}
Q_SLOT void error(QString errorString){
labelStatus.setText(errorString);
}
Q_SLOT void updateProgress(qint64 bytesReceived, qint64 bytesTotal){
progressBar.setRange(0, bytesTotal);
progressBar.setValue(bytesReceived);
}
private:
Q_SLOT void showBusy(){
buttonDownload.setEnabled(false);
lineEditUrl.setEnabled(false);
buttonAbort.setEnabled(true);
labelStatus.setText("Downloading. . .");
}
Q_SLOT void showReady(){
buttonDownload.setEnabled(true);
lineEditUrl.setEnabled(true);
buttonAbort.setEnabled(false);
progressBar.setRange(0,1);
progressBar.setValue(0);
}
QGridLayout layout{this};
QLineEdit lineEditUrl;
QPushButton buttonDownload{"Start Download"};
QProgressBar progressBar;
QPushButton buttonAbort{"Abort Download"};
QLabel labelStatus{"Idle"};
QNetworkAccessManager nam;
FileDownloader fileDownloader{&nam};
};
int main(int argc, char* argv[]){
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
I'm trying to create a test that will use a QNetworkAccessManager to talk with our RESTful api. All I want to do is grab a simple JSon object using a QNetworkAccessManager. My code looks like this:
Connection::Connection(QString const &username, QString const &password, QString const &url, QString const &api) :
_user(username), _pass(password), _url(url) {
_manager = new QNetworkAccessManager(this);
QObject::connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(receiveReply(QNetworkReply*)));
QNetworkRequest request;
request.setUrl(QUrl(_url.append(api)));
request.setRawHeader("Authorization", QString("Basic ").append(QString("%1:%2").arg(_user).arg(_pass)).toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json");
QNetworkReply *reply = _manager->get(request);
}
void Connection::Connection(QNetworkReply *reply) {
//do some validation of the reply...
_data = QJsonDocument::fromJson(reply->readAll());
}
QJsonDocument Connection::data() const {
return _data;
}
...
#include <gtest/gtest.h>
#include "Connection.h"
#include <QApplication>
TEST(ConnectionTest, creation) {
int argc = 0;
char *argv = 0;
QApplication a(argc, &argv);
Connection *connect = new Connection("user","abc123", https://mytest.com/", "api/");
connect->deleteLater();
while (connect->data().isEmpty()) {
//loop forever
}
}
Originally I was testing by having Connection also be a QWidget. The finished signal would only show if I called connection->show(); Then I have to close the widget manually and the test completes. However, this isn't really useful for automated unit testing.
If I use the above while loop to wait until _data has been set to something then nothing ever happens with the QNetworkAccessManager and it loops forever.
Is there some call that needs to happen before anything will move forward? Why was ->show() 'working?'
You're not spinning the event loop, so don't expect anything asynchronous to work at all.
You need to add a signal to the Connection class that indicates when it's done, and use that signal to quit the event loop:
TEST(ConnectionTest, creation) {
int argc = 0;
char *argv = 0;
QCoreApplication app(argc, &argv);
// No need to allocate the connection on the heap!
Connection connection("user", "abc123", "https://mytest.com/", "api/");
QObject::connect(&connection, &Connection::finished,
&app, &QCoreApplication::quit());
app.exec();
}
You could add the QApplication instance as a member of the ConnectionTest class.
There's also no need to double the number of heap allocations by having the QNetworkAccessManager * manager member. You can have it as a regular member:
class Connection : public QObject {
Q_OBJECT
QNetworkAccessManager manager;
Q_SLOT void receiveReply(QNetworkReply *) {
...
emit finished();
}
public:
explicit
Connection(const QString & user, const QString & password,
const QUrl & url, const QString & path,
QObject * parent = 0) : QObject(parent) { ... }
Q_SIGNAL void finished();
};
I'm trying to display a sequence of images through a Qlabel using setPixmap. I have a QStringList containing the image file names and a for loop which iterates through the images with a 5 second wait after each image. However, only the last image file is ever being displayed. Currently the screen remains blank during the wait of the first iterations until the last image is finally shown. I've read that using a for loop wont work and that I should be using signals and slots instead. I'm new to this concept though and I would really appreciate an example to point me in the right direction.
Here is my current code:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),ui(new Ui::MainWindow){
ui->setupUi(this);
QStringList images;
QString imageName;
images << "redScreen.png" << "blueScreen.png" << "greenScreen.png";
for(int x=0; x < images.size(); x++){
imageName = images.at(x);
this->displayScreen(imageName, 5);
}
}
void MainWindow::displayScreen(QString imageName, int wait){
QTimer t;
QEventLoop loop;
QPixmap myImage;
myImage.load(":/images/" + imageName);
ui->imageLabel->setPixmap(myImage);
ui->imageLabel->repaint();
// 5 second wait between next iteration
t.start(wait*1000);
connect(&t, SIGNAL(timeout()), &loop, SLOT(quit()));
loop.exec();
}
The reentrant wait-via-eventloop hack is a great source of hard-to-diagnose bugs. Don't use it. It's very, very rare that you'll need to instantiate your own event loop. Even rather complex projects can entirely avoid it.
You should simply run a timer and react to timer ticks. Here's one example:
#include <QApplication>
#include <QImage>
#include <QGridLayout>
#include <QLabel>
#include <QBasicTimer>
class Widget : public QWidget {
QGridLayout m_layout;
QLabel m_name, m_image;
QStringList m_images;
QStringList::const_iterator m_imageIt;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() == m_timer.timerId()) tick();
}
void tick() {
display(*m_imageIt);
m_imageIt ++;
const bool loop = false;
if (m_imageIt == m_images.end()) {
if (loop)
m_imageIt = m_images.begin();
else
m_timer.stop();
}
}
void display(const QString & imageName) {
QImage img(":/images/" + imageName);
m_name.setText(imageName);
m_image.setPixmap(QPixmap::fromImage(img));
}
public:
Widget(QWidget * parent = 0) : QWidget(parent), m_layout(this) {
m_images << "redScreen.png" << "blueScreen.png" << "greenScreen.png";
m_imageIt = m_images.begin();
m_layout.addWidget(&m_name, 0, 0);
m_layout.addWidget(&m_image, 1, 0);
tick();
m_timer.start(5000, Qt::CoarseTimer, this);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
A code similar to the below must work for the task you mentioned. ( needs cleaning/class organization though )
QTimer timer;
int x=0;
QStringList images;
QString imageName;
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),ui(new Ui::MainWindow){
ui->setupUi(this);
images << "redScreen.png" << "blueScreen.png" << "greenScreen.png";
connect( &timer, SIGNAL(timeout()), this, SLOT(ChangeImageSlot()) );
timer.start(5000);
}
void ChangeImageSlot()
{
imageName = images.at(x++);
this->displayScreen(imageName, 5);
if( x < images.size() )
timer.start(5000);
}
Best solution is DeepBlack with a couple of QTimer, but if you want at you risk you can try insert an processEvent() function inside the for loop of display image.
i'm trying to send a QList as a parameter to another class but for some reason i lose all it's content ...
(when i open the object with the debuger i see for objects...)
trying to send QList books to class Print:
class Store: public QWidget {
Q_OBJECT
public:
Analyze(QWidget *parent = 0);
void generate_report();
~Analyze();
private:
QList<Book *> books;
};
class Print
{
public:
Print();
bool generate_report_file(QList<Book *> *);
};
i'm sending books like this:
void Analyze::generate_report()
{
.
.
.
Print p;
if (!p.generate_report_file(&books))
QMessageBox::warning(this, "XML Escape","Error creating out.html", QMessageBox::Ok);
}
Small example
#include <QtCore/QCoreApplication>
#include <QDebug>
#include <QList>
#include <QString>
void print_list(QList<QString *> * k)
{
for (int i=0; i<k->size(); i++)
{
qDebug() << *k->at(i);
}
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<QString *> books;
books.append(new QString("asd"));
books.append(new QString("asdfgh"));
books.append(new QString("asdjhhhhhhtyut"));
print_list (&books);
return a.exec();
}
so just use * in function when calling elements of your QList, like in
qDebug() << *k->at(i); string
You should pass the QList by value. The reason, while this may seem silly on the surface, is that QList is implicitly shared. Please read http://doc.trolltech.com/latest/implicit-sharing.html to see more on that topic.
#include <QtCore/QtCore>
void
printList(const QStringList& list)
{
foreach (const QString& str, list) {
qDebug() << str;
}
}
int main(int argc, char** argv) {
QCoreApplication app(argc, argv);
QStringList list;
list << "A" << "B" << "C";
printList(list);
return QCoreApplication::exec();
}
There is already a class called QStringList to use. Also, you would want to pass it by reference. Also, you do not want to use pointers on containers or QString. As they are automatically implicitly shared. So it's bad design to use pointers on those two.