CutyCapt not able to generate HTTPS web site screenshot when launched from w3wp.exe - asp.net

I have updated the latest code from SVN and complie CutyCapt with Qt SDK 4.8.
And I have static-linked libeay32.dll / ssleay32.dll in the same location of the binary.
It works fine if I launch the CutyCapt process in command window.
It works fine if I launch the CutyCapt process from ASP.NET (w3wp) to capture HTTP web page.
it does NOT work if I launch the CutyCapt process from ASP.NET (w3wp) to capture HTTPS web page, always generates a blank image.
I doubted it was because the libeay32.dll / ssleay32.dll dependencies are not loaded, so I copied these 2 dll to system32 / SysWOW64, this does not resolve my problem.
Then I monitor the process by ProcessMonitor, it shows me that libeay32.dll / ssleay32.dll are loaded successfully.
So, the dependencies are not the reason.
Here is my C# code to launch the CutyCapt,
using (Process process = new Process())
{
process.StartInfo.FileName = ti.ExeFile;
process.StartInfo.ErrorDialog = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(ti.ExeFile);
process.StartInfo.UseShellExecute = false;
process.StartInfo.Arguments = string.Format(#"--min-width=1024 --delay=1500 --javascript=off --auto-load-images=on --plugins=off --java=off --url={0} --out={1}"
, ti.Url
, destFile
);
process.Start();
process.WaitForExit();
process.Close();
}
Does anyone knows how to allow CutyCapt works from w3wp?

SOLUTION:
I have resolved this problem by modifying the CutyCapt source.
The idea is that, get the HTML of the url then set the HTML to the WebKit.
CutyCapt.hpp
#include <QtWebKit>
class CutyCapt;
class CutyPage : public QWebPage {
Q_OBJECT
public:
void setAttribute(QWebSettings::WebAttribute option, const QString& value);
void setUserAgent(const QString& userAgent);
void setAlertString(const QString& alertString);
void setPrintAlerts(bool printAlerts);
void setCutyCapt(CutyCapt* cutyCapt);
QString getAlertString();
protected:
QString chooseFile(QWebFrame *frame, const QString& suggestedFile);
void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID);
bool javaScriptPrompt(QWebFrame* frame, const QString& msg, const QString& defaultValue, QString* result);
void javaScriptAlert(QWebFrame* frame, const QString& msg);
bool javaScriptConfirm(QWebFrame* frame, const QString& msg);
QString userAgentForUrl(const QUrl& url) const;
QString mUserAgent;
QString mAlertString;
bool mPrintAlerts;
CutyCapt* mCutyCapt;
};
class CutyCapt : public QObject {
Q_OBJECT
public:
QString mUrl;
// TODO: This should really be elsewhere and be named differently
enum OutputFormat { SvgFormat, PdfFormat, PsFormat, InnerTextFormat, HtmlFormat,
RenderTreeFormat, PngFormat, JpegFormat, MngFormat, TiffFormat, GifFormat,
BmpFormat, PpmFormat, XbmFormat, XpmFormat, OtherFormat };
CutyCapt(CutyPage* page,
const QString& output,
int delay,
OutputFormat format,
const QString& scriptProp,
const QString& scriptCode);
private slots:
void HtmlDownloadFinished(QNetworkReply * reply);
void HtmlDownloadError(QNetworkReply::NetworkError code);
void DocumentComplete(bool ok);
void InitialLayoutCompleted();
void JavaScriptWindowObjectCleared();
void Timeout();
void Delayed();
private:
void TryDelayedRender();
void saveSnapshot();
bool mSawInitialLayout;
bool mSawDocumentComplete;
protected:
QString mOutput;
int mDelay;
CutyPage* mPage;
OutputFormat mFormat;
QObject* mScriptObj;
QString mScriptProp;
QString mScriptCode;
};
CutyCapt.cpp
////////////////////////////////////////////////////////////////////
//
// CutyCapt - A Qt WebKit Web Page Rendering Capture Utility
//
// Copyright (C) 2003-2010 Bjoern Hoehrmann <bjoern#hoehrmann.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// $Id: CutyCapt.cpp 7 2011-11-30 10:56:34Z hoehrmann $
//
////////////////////////////////////////////////////////////////////
#include <QApplication>
#include <QtWebKit>
#include <QtGui>
#include <QSvgGenerator>
#include <QPrinter>
#include <QTimer>
#include <QByteArray>
#include <QNetworkRequest>
#include <QtCore>
#include "CutyCapt.hpp"
#if QT_VERSION >= 0x040600 && 0
#define CUTYCAPT_SCRIPT 1
#endif
#ifdef STATIC_PLUGINS
Q_IMPORT_PLUGIN(qjpeg)
Q_IMPORT_PLUGIN(qgif)
Q_IMPORT_PLUGIN(qtiff)
Q_IMPORT_PLUGIN(qsvg)
Q_IMPORT_PLUGIN(qmng)
Q_IMPORT_PLUGIN(qico)
#endif
static struct _CutyExtMap {
CutyCapt::OutputFormat id;
const char* extension;
const char* identifier;
} const CutyExtMap[] = {
{ CutyCapt::SvgFormat, ".svg", "svg" },
{ CutyCapt::PdfFormat, ".pdf", "pdf" },
{ CutyCapt::PsFormat, ".ps", "ps" },
{ CutyCapt::InnerTextFormat, ".txt", "itext" },
{ CutyCapt::HtmlFormat, ".html", "html" },
{ CutyCapt::RenderTreeFormat, ".rtree", "rtree" },
{ CutyCapt::JpegFormat, ".jpeg", "jpeg" },
{ CutyCapt::PngFormat, ".png", "png" },
{ CutyCapt::MngFormat, ".mng", "mng" },
{ CutyCapt::TiffFormat, ".tiff", "tiff" },
{ CutyCapt::GifFormat, ".gif", "gif" },
{ CutyCapt::BmpFormat, ".bmp", "bmp" },
{ CutyCapt::PpmFormat, ".ppm", "ppm" },
{ CutyCapt::XbmFormat, ".xbm", "xbm" },
{ CutyCapt::XpmFormat, ".xpm", "xpm" },
{ CutyCapt::OtherFormat, "", "" }
};
QString
CutyPage::chooseFile(QWebFrame* /*frame*/, const QString& /*suggestedFile*/) {
return QString::null;
}
bool
CutyPage::javaScriptConfirm(QWebFrame* /*frame*/, const QString& /*msg*/) {
return true;
}
bool
CutyPage::javaScriptPrompt(QWebFrame* /*frame*/,
const QString& /*msg*/,
const QString& /*defaultValue*/,
QString* /*result*/) {
return true;
}
void
CutyPage::javaScriptConsoleMessage(const QString& /*message*/,
int /*lineNumber*/,
const QString& /*sourceID*/) {
// noop
}
void
CutyPage::javaScriptAlert(QWebFrame* /*frame*/, const QString& msg) {
if (mPrintAlerts)
qDebug() << "[alert]" << msg;
if (mAlertString == msg) {
QTimer::singleShot(10, mCutyCapt, SLOT(Delayed()));
}
}
QString
CutyPage::userAgentForUrl(const QUrl& url) const {
if (!mUserAgent.isNull())
return mUserAgent;
return QWebPage::userAgentForUrl(url);
}
void
CutyPage::setUserAgent(const QString& userAgent) {
mUserAgent = userAgent;
}
void
CutyPage::setAlertString(const QString& alertString) {
mAlertString = alertString;
}
QString
CutyPage::getAlertString() {
return mAlertString;
}
void
CutyPage::setCutyCapt(CutyCapt* cutyCapt) {
mCutyCapt = cutyCapt;
}
void
CutyPage::setPrintAlerts(bool printAlerts) {
mPrintAlerts = printAlerts;
}
void
CutyPage::setAttribute(QWebSettings::WebAttribute option,
const QString& value) {
if (value == "on")
settings()->setAttribute(option, true);
else if (value == "off")
settings()->setAttribute(option, false);
else
(void)0; // TODO: ...
}
// TODO: Consider merging some of main() and CutyCap
CutyCapt::CutyCapt(CutyPage* page, const QString& output, int delay, OutputFormat format,
const QString& scriptProp, const QString& scriptCode) {
mPage = page;
mOutput = output;
mDelay = delay;
mSawInitialLayout = false;
mSawDocumentComplete = false;
mFormat = format;
mScriptProp = scriptProp;
mScriptCode = scriptCode;
mScriptObj = new QObject();
// This is not really nice, but some restructuring work is
// needed anyway, so this should not be that bad for now.
mPage->setCutyCapt(this);
}
void
CutyCapt::InitialLayoutCompleted() {
mSawInitialLayout = true;
if (mSawInitialLayout && mSawDocumentComplete)
TryDelayedRender();
}
void
CutyCapt::DocumentComplete(bool /*ok*/) {
mSawDocumentComplete = true;
if (mSawInitialLayout && mSawDocumentComplete)
TryDelayedRender();
}
void
CutyCapt::JavaScriptWindowObjectCleared() {
if (!mScriptProp.isEmpty()) {
QVariant var = mPage->mainFrame()->evaluateJavaScript(mScriptProp);
QObject* obj = var.value<QObject*>();
if (obj == mScriptObj)
return;
mPage->mainFrame()->addToJavaScriptWindowObject(mScriptProp, mScriptObj);
}
mPage->mainFrame()->evaluateJavaScript(mScriptCode);
}
void CutyCapt::HtmlDownloadFinished(QNetworkReply * reply)
{
QVariant statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
qDebug() << statusCode;
if ( !statusCode.isValid() )
{
QApplication::exit();
return;
}
int status = statusCode.toInt();
if( status == 301 || status == 302 )
{
QVariant loc = reply->header(QNetworkRequest::LocationHeader);
qDebug() << "Location :" << loc.toString();
QApplication::exit();
return;
}
if ( status != 200 )
{
QString reason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();
qDebug() << reason;
QApplication::exit();
return;
}
QString html=QString::fromUtf8(reply->readAll());
mPage->mainFrame()->setHtml( html, QUrl(mUrl) );
}
void CutyCapt::HtmlDownloadError(QNetworkReply::NetworkError code)
{
QApplication::exit();
}
void
CutyCapt::TryDelayedRender() {
if (!mPage->getAlertString().isEmpty())
return;
if (mDelay > 0) {
QTimer::singleShot(mDelay, this, SLOT(Delayed()));
return;
}
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::Timeout() {
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::Delayed() {
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::saveSnapshot() {
QWebFrame *mainFrame = mPage->mainFrame();
QPainter painter;
const char* format = NULL;
for (int ix = 0; CutyExtMap[ix].id != OtherFormat; ++ix)
if (CutyExtMap[ix].id == mFormat)
format = CutyExtMap[ix].identifier; //, break;
// TODO: sometimes contents/viewport can have size 0x0
// in which case saving them will fail. This is likely
// the result of the method being called too early. So
// far I've been unable to find a workaround, except
// using --delay with some substantial wait time. I've
// tried to resize multiple time, make a fake render,
// check for other events... This is primarily a problem
// under my Ubuntu virtual machine.
mPage->setViewportSize( mainFrame->contentsSize() );
switch (mFormat) {
case SvgFormat: {
QSvgGenerator svg;
svg.setFileName(mOutput);
svg.setSize(mPage->viewportSize());
painter.begin(&svg);
mainFrame->render(&painter);
painter.end();
break;
}
case PdfFormat:
case PsFormat: {
QPrinter printer;
printer.setPageSize(QPrinter::A4);
printer.setOutputFileName(mOutput);
// TODO: change quality here?
mainFrame->print(&printer);
break;
}
case RenderTreeFormat:
case InnerTextFormat:
case HtmlFormat: {
QFile file(mOutput);
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream s(&file);
s.setCodec("utf-8");
s << (mFormat == RenderTreeFormat ? mainFrame->renderTreeDump() :
mFormat == InnerTextFormat ? mainFrame->toPlainText() :
mFormat == HtmlFormat ? mainFrame->toHtml() :
"bug");
break;
}
default: {
QImage image(mPage->viewportSize(), QImage::Format_ARGB32);
painter.begin(&image);
mainFrame->render(&painter);
painter.end();
// TODO: add quality
image.save(mOutput, format);
}
};
}
void
CaptHelp(void) {
printf("%s",
" -----------------------------------------------------------------------------\n"
" Usage: CutyCapt --url=http://www.example.org/ --out=localfile.png \n"
" -----------------------------------------------------------------------------\n"
" --help Print this help page and exit \n"
" --url=<url> The URL to capture (http:...|file:...|...) \n"
" --out=<path> The target file (.png|pdf|ps|svg|jpeg|...) \n"
" --out-format=<f> Like extension in --out, overrides heuristic \n"
// " --out-quality=<int> Output format quality from 1 to 100 \n"
" --min-width=<int> Minimal width for the image (default: 800) \n"
" --min-height=<int> Minimal height for the image (default: 600) \n"
" --max-wait=<ms> Don't wait more than (default: 90000, inf: 0)\n"
" --delay=<ms> After successful load, wait (default: 0) \n"
// " --user-styles=<url> Location of user style sheet (deprecated) \n"
" --user-style-path=<path> Location of user style sheet file, if any \n"
" --user-style-string=<css> User style rules specified as text \n"
" --header=<name>:<value> request header; repeatable; some can't be set\n"
" --method=<get|post|put> Specifies the request method (default: get) \n"
" --body-string=<string> Unencoded request body (default: none) \n"
" --body-base64=<base64> Base64-encoded request body (default: none) \n"
" --app-name=<name> appName used in User-Agent; default is none \n"
" --app-version=<version> appVers used in User-Agent; default is none \n"
" --user-agent=<string> Override the User-Agent header Qt would set \n"
" --javascript=<on|off> JavaScript execution (default: on) \n"
" --java=<on|off> Java execution (default: unknown) \n"
" --plugins=<on|off> Plugin execution (default: unknown) \n"
" --private-browsing=<on|off> Private browsing (default: unknown) \n"
" --auto-load-images=<on|off> Automatic image loading (default: on) \n"
" --js-can-open-windows=<on|off> Script can open windows? (default: unknown) \n"
" --js-can-access-clipboard=<on|off> Script clipboard privs (default: unknown)\n"
#if QT_VERSION >= 0x040500
" --print-backgrounds=<on|off> Backgrounds in PDF/PS output (default: off) \n"
" --zoom-factor=<float> Page zoom factor (default: no zooming) \n"
" --zoom-text-only=<on|off> Whether to zoom only the text (default: off) \n"
" --http-proxy=<url> Address for HTTP proxy server (default: none)\n"
#endif
#if CUTYCAPT_SCRIPT
" --inject-script=<path> JavaScript that will be injected into pages \n"
" --script-object=<string> Property to hold state for injected script \n"
" --expect-alert=<string> Try waiting for alert(string) before capture \n"
" --debug-print-alerts Prints out alert(...) strings for debugging. \n"
#endif
" -----------------------------------------------------------------------------\n"
" <f> is svg,ps,pdf,itext,html,rtree,png,jpeg,mng,tiff,gif,bmp,ppm,xbm,xpm \n"
" -----------------------------------------------------------------------------\n"
#if CUTYCAPT_SCRIPT
" The `inject-script` option can be used to inject script code into loaded web \n"
" pages. The code is called whenever the `javaScriptWindowObjectCleared` signal\n"
" is received. When `script-object` is set, an object under the specified name \n"
" will be available to the script to maintain state across page loads. When the\n"
" `expect-alert` option is specified, the shot will be taken when a script in- \n"
" vokes alert(string) with the string if that happens before `max-wait`. These \n"
" options effectively allow you to remote control the browser and the web page.\n"
" This an experimental and easily abused and misused feature. Use with caution.\n"
" -----------------------------------------------------------------------------\n"
#endif
" http://cutycapt.sf.net - (c) 2003-2010 Bjoern Hoehrmann - bjoern#hoehrmann.de\n"
"");
}
int
main(int argc, char *argv[]) {
int argHelp = 0;
int argDelay = 0;
int argSilent = 0;
int argMinWidth = 800;
int argMinHeight = 600;
int argMaxWait = 90000;
int argVerbosity = 0;
const char* argUrl = NULL;
const char* argUserStyle = NULL;
const char* argUserStylePath = NULL;
const char* argUserStyleString = NULL;
const char* argIconDbPath = NULL;
const char* argInjectScript = NULL;
const char* argScriptObject = NULL;
QString argOut;
CutyCapt::OutputFormat format = CutyCapt::OtherFormat;
QApplication app(argc, argv, true);
CutyPage page;
QNetworkAccessManager::Operation method =
QNetworkAccessManager::GetOperation;
QByteArray body;
QNetworkRequest req;
QNetworkAccessManager manager;
// Parse command line parameters
for (int ax = 1; ax < argc; ++ax) {
size_t nlen;
const char* s = argv[ax];
const char* value;
// boolean options
if (strcmp("--silent", s) == 0) {
argSilent = 1;
continue;
} else if (strcmp("--help", s) == 0) {
argHelp = 1;
break;
} else if (strcmp("--verbose", s) == 0) {
argVerbosity++;
continue;
#if CUTYCAPT_SCRIPT
} else if (strcmp("--debug-print-alerts", s) == 0) {
page.setPrintAlerts(true);
continue;
#endif
}
value = strchr(s, '=');
if (value == NULL) {
// TODO: error
argHelp = 1;
break;
}
nlen = value++ - s;
// --name=value options
if (strncmp("--url", s, nlen) == 0) {
argUrl = value;
} else if (strncmp("--min-width", s, nlen) == 0) {
// TODO: add error checking here?
argMinWidth = (unsigned int)atoi(value);
} else if (strncmp("--min-height", s, nlen) == 0) {
// TODO: add error checking here?
argMinHeight = (unsigned int)atoi(value);
} else if (strncmp("--delay", s, nlen) == 0) {
// TODO: see above
argDelay = (unsigned int)atoi(value);
} else if (strncmp("--max-wait", s, nlen) == 0) {
// TODO: see above
argMaxWait = (unsigned int)atoi(value);
} else if (strncmp("--out", s, nlen) == 0) {
argOut = value;
if (format == CutyCapt::OtherFormat)
for (int ix = 0; CutyExtMap[ix].id != CutyCapt::OtherFormat; ++ix)
if (argOut.endsWith(CutyExtMap[ix].extension))
format = CutyExtMap[ix].id; //, break;
} else if (strncmp("--user-styles", s, nlen) == 0) {
// This option is provided for backwards-compatibility only
argUserStyle = value;
} else if (strncmp("--user-style-path", s, nlen) == 0) {
argUserStylePath = value;
} else if (strncmp("--user-style-string", s, nlen) == 0) {
argUserStyleString = value;
} else if (strncmp("--icon-database-path", s, nlen) == 0) {
argIconDbPath = value;
} else if (strncmp("--auto-load-images", s, nlen) == 0) {
page.setAttribute(QWebSettings::AutoLoadImages, value);
} else if (strncmp("--javascript", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptEnabled, value);
} else if (strncmp("--java", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavaEnabled, value);
} else if (strncmp("--plugins", s, nlen) == 0) {
page.setAttribute(QWebSettings::PluginsEnabled, value);
} else if (strncmp("--private-browsing", s, nlen) == 0) {
page.setAttribute(QWebSettings::PrivateBrowsingEnabled, value);
} else if (strncmp("--js-can-open-windows", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptCanOpenWindows, value);
} else if (strncmp("--js-can-access-clipboard", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptCanAccessClipboard, value);
} else if (strncmp("--developer-extras", s, nlen) == 0) {
page.setAttribute(QWebSettings::DeveloperExtrasEnabled, value);
} else if (strncmp("--links-included-in-focus-chain", s, nlen) == 0) {
page.setAttribute(QWebSettings::LinksIncludedInFocusChain, value);
#if QT_VERSION >= 0x040500
} else if (strncmp("--print-backgrounds", s, nlen) == 0) {
page.setAttribute(QWebSettings::PrintElementBackgrounds, value);
} else if (strncmp("--zoom-factor", s, nlen) == 0) {
page.mainFrame()->setZoomFactor(QString(value).toFloat());
} else if (strncmp("--zoom-text-only", s, nlen) == 0) {
page.setAttribute(QWebSettings::ZoomTextOnly, value);
} else if (strncmp("--http-proxy", s, nlen) == 0) {
QUrl p = QUrl::fromEncoded(value);
QNetworkProxy proxy = QNetworkProxy(QNetworkProxy::HttpProxy,
p.host(), p.port(80), p.userName(), p.password());
manager.setProxy(proxy);
page.setNetworkAccessManager(&manager);
#endif
#if CUTYCAPT_SCRIPT
} else if (strncmp("--inject-script", s, nlen) == 0) {
argInjectScript = value;
} else if (strncmp("--script-object", s, nlen) == 0) {
argScriptObject = value;
} else if (strncmp("--expect-alert", s, nlen) == 0) {
page.setAlertString(value);
#endif
} else if (strncmp("--app-name", s, nlen) == 0) {
app.setApplicationName(value);
} else if (strncmp("--app-version", s, nlen) == 0) {
app.setApplicationVersion(value);
} else if (strncmp("--body-base64", s, nlen) == 0) {
body = QByteArray::fromBase64(value);
} else if (strncmp("--body-string", s, nlen) == 0) {
body = QByteArray(value);
} else if (strncmp("--user-agent", s, nlen) == 0) {
page.setUserAgent(value);
} else if (strncmp("--out-format", s, nlen) == 0) {
for (int ix = 0; CutyExtMap[ix].id != CutyCapt::OtherFormat; ++ix)
if (strcmp(value, CutyExtMap[ix].identifier) == 0)
format = CutyExtMap[ix].id; //, break;
if (format == CutyCapt::OtherFormat) {
// TODO: error
argHelp = 1;
break;
}
} else if (strncmp("--header", s, nlen) == 0) {
const char* hv = strchr(value, ':');
if (hv == NULL) {
// TODO: error
argHelp = 1;
break;
}
req.setRawHeader(QByteArray(value, hv - value), hv + 1);
} else if (strncmp("--method", s, nlen) == 0) {
if (strcmp("value", "get") == 0)
method = QNetworkAccessManager::GetOperation;
else if (strcmp("value", "put") == 0)
method = QNetworkAccessManager::PutOperation;
else if (strcmp("value", "post") == 0)
method = QNetworkAccessManager::PostOperation;
else if (strcmp("value", "head") == 0)
method = QNetworkAccessManager::HeadOperation;
else
(void)0; // TODO: ...
} else {
// TODO: error
argHelp = 1;
}
}
if (argUrl == NULL || argOut == NULL || argHelp) {
CaptHelp();
return EXIT_FAILURE;
}
// This used to use QUrl(argUrl) but that escapes %hh sequences
// even though it should not, as URLs can assumed to be escaped.
req.setUrl( QUrl::fromEncoded(argUrl) );
QString scriptProp(argScriptObject);
QString scriptCode;
if (argInjectScript) {
QFile file(argInjectScript);
if (file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream.setAutoDetectUnicode(true);
scriptCode = stream.readAll();
file.close();
}
}
CutyCapt main(&page, argOut, argDelay, format, scriptProp, scriptCode);
main.mUrl = argUrl;
app.connect(&page,
SIGNAL(loadFinished(bool)),
&main,
SLOT(DocumentComplete(bool)));
app.connect(page.mainFrame(),
SIGNAL(initialLayoutCompleted()),
&main,
SLOT(InitialLayoutCompleted()));
if (argMaxWait > 0) {
// TODO: Should this also register one for the application?
QTimer::singleShot(argMaxWait, &main, SLOT(Timeout()));
}
if (argUserStyle != NULL)
// TODO: does this need any syntax checking?
page.settings()->setUserStyleSheetUrl( QUrl::fromEncoded(argUserStyle) );
if (argUserStylePath != NULL) {
page.settings()->setUserStyleSheetUrl( QUrl::fromLocalFile(argUserStylePath) );
}
if (argUserStyleString != NULL) {
QUrl data("data:text/css;charset=utf-8;base64," +
QByteArray(argUserStyleString).toBase64());
page.settings()->setUserStyleSheetUrl( data );
}
if (argIconDbPath != NULL)
// TODO: does this need any syntax checking?
page.settings()->setIconDatabasePath(argUserStyle);
// The documentation does not say, but it seems the mainFrame
// will never change, so we can set this here. Otherwise we'd
// have to set this in snapshot and trigger an update, which
// is not currently possible (Qt 4.4.0) as far as I can tell.
page.mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
page.mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
page.setViewportSize( QSize(argMinWidth, argMinHeight) );
#if CUTYCAPT_SCRIPT
// javaScriptWindowObjectCleared does not get called on the
// initial load unless some JavaScript has been executed.
page.mainFrame()->evaluateJavaScript(QString(""));
app.connect(page.mainFrame(),
SIGNAL(javaScriptWindowObjectCleared()),
&main,
SLOT(JavaScriptWindowObjectCleared()));
#endif
app.connect( &manager,SIGNAL(finished(QNetworkReply *)), &main,SLOT(HtmlDownloadFinished(QNetworkReply *)));
QNetworkReply *reply = manager.get(req);
app.connect( reply,SIGNAL(error(QNetworkReply::NetworkError)), &main,SLOT(HtmlDownloadError(QNetworkReply::NetworkError)));
return app.exec();
}

Related

How to create a QVariant-based generic model?

Quite often I find myself in need of some custom scheme model, mandating the implementation of more and more models, made even more tedious by the inability of QObject derived classes to be templates.
Qt has the QStandardItemModel but that seems a bit verbose and inconvenient to use, especially from the qml side, and total overkill for a basic list model.
There is also the basic qml ListModel, but that is limited and not elegant to use on the C++ side, and I do suspect a tad more bloated than it needs to be.
Qt has QVariant, which is what its model/view architecture uses internally, so it is surprising that the framework doesn't provide something as simple as:
// qml code
VarMod {
roles: ["name", "age", "weight"]
Component.onCompleted: {
insert(["Jack", 34, 88.5], -1) // qml doesn't support
insert(["Mary", 26, 55.3], -1) // default arg values
}
}
// cpp code
VarMod vm { "name", "age", "weight" }; // member declaration
vm.insert({ "Jack", 34, 88.5 });
vm.insert({ "Mary", 26, 55.3 });
And here it is.
Note that you do have to be responsible with the parameters, as there is no type safety, in fact it has implicit analog to ListModel's dynamicRoles - that is, it will accept and work with any QVariant compatible value on every role slot.
As for memory efficiency, consider that QVariant has 8 bytes for data, plus 4 bytes for type id, plus another 4 bytes of padding, for a total of 16 bytes. That is not insignificant if you are using it for small data types, like say bool, so in case you have a data scheme that has a lot of small (1 - 4 bytes) fields and a scores of items, implementing a full model will still be the better option. It is still a lot better than the generic object model I am using, which has to carry the bloat of QObject, and even more significant in the case of qml objects.
Additionally, QVariant being 16 bytes, I opted to not use the convenience of QVariantList for data storage, which has an underlying QList, making the situation worse than it needs to be. Although that is fixed in Qt 6, which gets rid of QList as it is, and replaces it with an alias of QVector. Still, std::vector helps to avoid that in any case, plus it might actually be a tad faster, since it doesn't have to deal with COW and atomic ref counters. There are several auxiliary methods to help with pre-allocation and release of memory as well.
The model has a safeguard against the change the roles for obvious reasons, the latter is primarily intended to be initialized just once, but there is reset() that is intended to be used in a more dynamic qml context, making it possible to redefine the model schema on the fly and provide a compatible delegate. For the sake of certainty, the roles can only be redefined after the model has been explicitly reset.
There is a minute difference in inserting, on the c++ side, the parameter pack is passed wrapped in {}, in qml it is wrapped in [], both leveraging implicit conversion in the context specific way. Also, note that qml currently doesn't support omitting parameters with default values provided on the c++ side, so for appending you do have to provide an invalid index. Naturally, it would be trivial to add convenience methods for appending and prepending if needed.
In addition to the syntax example of the question, it is also possible to add multiple items at once, from "declarative-y" qml structure such as:
let v = [["Jack", 34, 88.5],
["Mary", 26, 55.3],
["Sue", 22, 69.6]]
vm.insertList(v, -1)
Finally, type safety is possible to implement if the scenario really calls for it, then each role can be specified with the expected type to go with it, such as:
VarMod vm {{"name", QMetaType::QString},
{"age", QMetaType::Int},
{"weight", QMetaType::QReal}};
and then iterating and making the necessary checks to ensure type safety when inserting.
Update: I also added serialization, and save/load from disk features, note that this will serialize the data together with the mode schema.
class VarMod : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
QVariantList vroles;
QVariantList roles() const { return vroles; }
QHash<int, QByteArray> _roles;
std::vector<std::vector<QVariant>> _data;
inline bool checkArgs(int rc) const {
if (rc == _roles.size()) return true;
qWarning() << "arg size mismatch, got / expected" << rc << _roles.size();
return false;
}
inline bool inBounds(int i, bool ok = false) const {
if (i > -1 && i < (int)_data.size()) return true;
if (!ok) qWarning() << "out of bounds" << i; // do not warn if intentionally appending
return false;
}
inline bool validRole(int r) const { return (r > -1 && r < _roles.size()); }
protected:
QHash<int, QByteArray> roleNames() const override { return _roles; }
int rowCount(const QModelIndex &) const override { return _data.size(); }
QVariant data(const QModelIndex &index, int r) const override {
r = r - Qt::UserRole - 1;
if (inBounds(index.row()) && validRole(r)) return _data[index.row()][r];
return QVariant();
}
public:
VarMod() {} // for qml
VarMod(std::initializer_list<QByteArray> r) {
int rc = Qt::UserRole + 1;
for (const auto & ri : r) {
_roles.insert(rc++, ri);
vroles << QString::fromLatin1(ri);
}
rolesChanged();
}
inline void insert(std::initializer_list<QVariant> s, int i = -1) {
if (!checkArgs(s.size())) return;
insert(QVariantList(s), i);
}
inline bool setItem(int i, std::initializer_list<QVariant> s) {
if (checkArgs(s.size())) return setItem(i, QVariantList(s));
return false;
}
void setRoles(QVariantList r) {
if (_roles.empty()) {
int rc = Qt::UserRole + 1;
for (const auto & vi : r) _roles.insert(rc++, vi.toByteArray());
vroles = r;
rolesChanged();
} else qWarning() << "roles are already initialized";
}
void read(QDataStream & d) {
reset();
QVariantList vr;
d >> vr;
quint32 s;
d >> s;
_data.resize(s);
for (uint i = 0; i < s; ++i) {
_data[i].reserve(vr.size());
for (int c = 0; c < vr.size(); ++c) {
QVariant var;
d >> var;
_data[i].push_back(std::move(var));
}
}
setRoles(vr);
beginResetModel();
endResetModel();
}
void write(QDataStream & d) const {
d << vroles;
d << (quint32)_data.size();
for (const auto & v : _data) {
for (const auto & i : v) d << i;
}
}
public slots:
void insert(QVariantList s, int i) {
if (!inBounds(i, true)) i = _data.size();
if (!checkArgs(s.size())) return;
beginInsertRows(QModelIndex(), i, i);
_data.insert(_data.begin() + i, { s.cbegin(), s.cend() });
endInsertRows();
}
void insertList(QVariantList s, int i) {
if (!inBounds(i, true)) i = _data.size();
int added = 0;
for (const auto & il : s) {
QVariantList ll = il.value<QVariantList>();
if (checkArgs(ll.size())) {
_data.insert(_data.begin() + i + added++, { ll.cbegin(), ll.cend() });
}
}
if (added) {
beginInsertRows(QModelIndex(), i, i + added - 1);
endInsertRows();
}
}
bool setData(int i, int r, QVariant d) {
if (!inBounds(i) || !validRole(r)) return false;
_data[i][r] = d;
dataChanged(index(i), index(i));
return true;
}
bool setDataStr(int i, QString rs, QVariant d) { // a tad slower
int r = _roles.key(rs.toLatin1()); // role is resolved in linear time
if (r) return setData(i, r - Qt::UserRole - 1, d);
qWarning() << "invalid role" << rs;
return false;
}
bool setItem(int i, QVariantList d) {
if (!inBounds(i) || !checkArgs(d.size())) return false;
_data[i] = { d.cbegin(), d.cend() };
dataChanged(index(i), index(i));
return true;
}
QVariantList item(int i) const {
if (!inBounds(i)) return QVariantList();
const auto & v = _data[i];
return { v.begin(), v.end() };
}
QVariant getData(int i, int r) const {
if (inBounds(i) && validRole(r)) return _data[i][r];
return QVariant();
}
QVariant getDataStr(int i, QString rs) const {
int r = _roles.key(rs.toLatin1()); // role is resolved in linear time
if (r) return getData(i, r);
qWarning() << "invalid role" << rs;
return QVariant();
}
QVariantList take(int i) {
QVariantList res = item(i);
if (res.size()) remove(i);
return res;
}
bool swap(int i1, int i2) {
if (!inBounds(i1) || !inBounds(i2)) return false;
std::iter_swap(_data.begin() + i1, _data.begin() + i2);
dataChanged(index(i1), index(i1));
dataChanged(index(i2), index(i2));
return true;
}
bool remove(int i) {
if (!inBounds(i)) return false;
beginRemoveRows(QModelIndex(), i, i);
_data.erase(_data.begin() + i);
endRemoveRows();
return true;
}
void clear() {
beginResetModel();
_data.clear();
_data.shrink_to_fit();
endResetModel();
}
void reset() {
clear();
_roles.clear();
vroles.clear();
rolesChanged();
}
void reserve(int c) { _data.reserve(c); }
int size() const { return _data.size(); }
int capacity() const { return _data.capacity(); }
void squeeze() { _data.shrink_to_fit(); }
bool fromFile(QString path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) return false;
QDataStream d(&f);
read(d); // assumes correct data
return true;
}
bool toFile(QString path) const {
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return false;
QDataStream d(&f);
write(d);
return true;
}
signals:
void rolesChanged();
};
I also created this sorting/filtering view to supplement the model:
class View : public QSortFilterProxyModel {
Q_OBJECT
Q_PROPERTY(QJSValue filter READ filter WRITE set_filter NOTIFY filterChanged)
Q_PROPERTY(bool reverse READ reverse WRITE setReverse NOTIFY reverseChanged)
bool reverse() const { return _reverse; }
void setReverse(bool v) {
if (v == _reverse) return;
_reverse = v;
reverseChanged();
sort(0, (Qt::SortOrder)_reverse);
}
bool _reverse = false;
mutable QJSValue m_filter;
QJSValue & filter() const { return m_filter; }
void set_filter(QJSValue & f) {
if (!m_filter.equals(f))
m_filter = f;
filterChanged();
invalidateFilter();
}
}
public:
View(QObject *parent = 0) : QSortFilterProxyModel(parent) { sort(0, (Qt::SortOrder)_reverse); }
signals:
void filterChanged();
void reverseChanged();
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override {
if (!m_filter.isCallable()) return true;
VarMod * vm = qobject_cast<VarMod *>(sourceModel());
if (!vm) {
qWarning() << "model is not varmod";
return true;
}
return m_filter.call({_engine->toScriptValue(vm->item(sourceRow))}).toBool();
}
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override {
VarMod * vm = qobject_cast<VarMod *>(sourceModel());
if (!vm) {
qWarning() << "model is not varmod";
return false;
}
return vm->getData(left.row(), sortRole()) < vm->getData(right.row(), sortRole());
}
};
For sorting, you just have to specify the sorting role, note that it is the index of the "column" rather than the int value from the roles hash. For filtering it works via a qml functor that receives the model item as a JS array, and expects to return a bool, a c++ functor can be easily added via std::function if needed. Also note that it needs a pointer to the actual qml engine.
View {
id: vv
sourceModel: vm
sortRole: sr.value
reverse: rev.checked
filter: { sa.value; o => o[1] < sa.value } // "capturing" sa.value to react to value changes
}

QLibrary fails to load .so file

I am trying to load a simple C based library interface that is contained of few functions, and compiled with qt as template lib. When I follow the standart way of loading the library, my QLibrary::load() fails. Here is the code:
bool RecPluginMngr::loadLibrary(const QString &src)
{
bool res = false;
static QLibrary plugin(src);
res = plugin.load();
if (!res) {
return res;
}
bool load_all_res = true;
RecIface iface;
iface.init = (init) plugin.resolve("init");
// todo: err messages !!!
if (iface.init == nullptr) {
load_all_res &= false;
}
iface.get_data = (get_data) plugin.resolve("get_data");
if (iface.get_data == nullptr) {
load_all_res &= false;
}
iface.put_data = (put_data) plugin.resolve("put_data");
if (iface.put_data == nullptr) {
load_all_res &= false;
}
iface.put_ndata = (put_ndata) plugin.resolve("put_ndata");
if (iface.put_ndata == nullptr) {
load_all_res &= false;
}
// map them all
m_plugins[src] = iface;
return load_all_res;
}

Qtwebkit persistence Cookie is not working

I am trying to implement a persistence in a Qtwebkit based browser for section keeping. I have extended QNetworkCookieJar. I didn't override any file but only added a save and load function. The functions is called explisitly.
I am getting the following error. The code is taken from brower code in Qt5.1 example.
QVariant::save: unable to save type 'QList<QNetworkCookie>' (type id: 1038).
Am I doing any thing wrong.. Please Help...Attaching my implementation of QNetworkCookie is bellow
static const unsigned int JAR_VERSION = 23;
#if 1
QT_BEGIN_NAMESPACE
QDataStream &operator<<(QDataStream &stream, const QList<QNetworkCookie> &list)
{
stream << JAR_VERSION;
stream << quint32(list.size());
for (int i = 0; i < list.size(); ++i)
stream << list.at(i).toRawForm();
return stream;
}
QDataStream &operator>>(QDataStream &stream, QList<QNetworkCookie> &list)
{
list.clear();
quint32 version;
stream >> version;
if (version != JAR_VERSION)
return stream;
quint32 count;
stream >> count;
for(quint32 i = 0; i < count; ++i)
{
QByteArray value;
stream >> value;
QList<QNetworkCookie> newCookies = QNetworkCookie::parseCookies(value);
if (newCookies.count() == 0 && value.length() != 0) {
qWarning() << "CookieJar: Unable to parse saved cookie:" << value;
}
for (int j = 0; j < newCookies.count(); ++j)
list.append(newCookies.at(j));
if (stream.atEnd())
break;
}
return stream;
}
QT_END_NAMESPACE
#endif
CookieJar::CookieJar(QObject *parent)
: QNetworkCookieJar(parent)
{
load();
}
CookieJar::~CookieJar()
{
save();
}
void CookieJar::load()
{
QSettings cookieSettings("./cookies_dir/Browser/cookies.ini", QSettings::IniFormat);
setAllCookies(qvariant_cast<QList<QNetworkCookie> >(cookieSettings.value(QLatin1String("cookies"))));
}
void CookieJar::save()
{
// purgeOldCookies();
QString directory = "./cookies_dir/Browser";
if (!QFile::exists(directory)) {
QDir dir;
dir.mkpath(directory);
}
QSettings cookieSettings(directory + QLatin1String("/cookies.ini"), QSettings::IniFormat);
QList<QNetworkCookie> cookies = allCookies();
qWarning("\n\n+=======================================================+\n\n");
for (int i = cookies.count() - 1; i >= 0; --i) {
qWarning()<< cookies.at(i).domain();
qWarning()<< cookies.at(i).name();
if (cookies.at(i).isSessionCookie())
cookies.removeAt(i);
}
qWarning("\n\n+=======================================================+\n\n");
cookieSettings.setValue(QLatin1String("cookies"), QVariant::fromValue<QList<QNetworkCookie> >(cookies));
}
void CookieJar::purgeOldCookies()
{
QList<QNetworkCookie> cookies = allCookies();
if (cookies.isEmpty())
return;
int oldCount = cookies.count();
QDateTime now = QDateTime::currentDateTime();
for (int i = cookies.count() - 1; i >= 0; --i) {
if (!cookies.at(i).isSessionCookie() && cookies.at(i).expirationDate() < now)
cookies.removeAt(i);
}
if (oldCount == cookies.count())
return;
setAllCookies(cookies);
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
qWarning()<< url;
return QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
}
I have registered the CookieJar using bellow code.
jar = new CookieJar(this);
webView->page()->networkAccessManager()->setCookieJar(jar);
You must call qRegisterMetaTypeStreamOperators<QList<QNetworkCookie>>("QList<QNetworkCookie>") before saving or loading to QSettings.
You need to use qRegisterMetaTypeStreamOperators and defines stream operators.
Here is an example save QList< QNetworkCookie> type cookies. In the header declares stream operators :
class cookieJar: public QNetworkCookieJar
{
bool save(const QUrl& url);
bool load(const QUrl& url);
}
QDataStream &operator<<(QDataStream &out, const QNetworkCookie &cookie);
QDataStream &operator>>(QDataStream &in, QNetworkCookie &cookie);
In cpp file you do something like:
CookieJar::CookieJar(QObject* parent) :
QNetworkCookieJar(parent)
{
qRegisterMetaTypeStreamOperators<QNetworkCookie>("QNetworkCookie");
}
bool CookieJar::save(const QUrl &url)
{
QList<QNetworkCookie> cookies = cookiesForUrl(url);
QSettings cookieSettings;
for(auto cookie : cookies)
{
cookieSettings.setValue(cookie.name(), QVariant::fromValue(cookie));
}
}
bool CookieJar::load(const QUrl &url)
{
QSettings cookieSettings;
// empty list created here
QList<QNetworkCookie> cookies;
// retrieve all keys from cookies.ini
QStringList keys = cookieSettings.allKeys();
for(auto key: keys)
{
QNetworkCookie cookie = qvariant_cast<QNetworkCookie>(cookieSettings.value(key, QVariant()));
cookies.append(cookie);
}
setCookiesFromUrl(cookies, url);
return true;
}
QDataStream &operator<<(QDataStream &out, const QNetworkCookie &cookie)
{
out << cookie.toRawForm();
}
QDataStream &operator>>(QDataStream &in, QNetworkCookie &cookie)
{
QByteArray raw_data;
in >> raw_data;
QList<QNetworkCookie> cookies = QNetworkCookie::parseCookies(raw_data);
cookie = cookies.at(0);
}

QSignalSpy to capture a reference argument

It is not possible to capture an argument that has been passed as reference with a QSignalSpy:
QSignalSpy spy( myObject, SIGNAL(foo(int&)));
...
int& i=spy.at(0).at(0).value<int&>();
Since a QVariant can not contain a reference member. Plain logic.
But are there other solutions to check the passed-in argument?
Since Qt 5, we can simply connect to a lambda function, which makes the use of the QSignalSpy unnecessary:
std::vector<Value> values;
QObject::connect(myObject, &MyObject::foo,
[&](const auto &value)
{ values.emplace_back(value); });
myObject.somethingCausingFoo();
ASSERT_EQ(1u, values.size());
EXPECT_EQ(expectedValue, values.at(0));
An "ugly solution" would be to hack the fairly simple QSignalSpy code in order to handle the reference passed arguments. I provide a minimal working example for int reference arguments. The only changes were made to initArgs and appendArgs functions.
Notice that with this approach you will only be able to check the value of the passed argument by reference. You will not be able to change it's value.
In the initArgs function we check if we have references by argument and we populate the shouldreinterpret list.
void initArgs(const QMetaMethod &member)
{
QList<QByteArray> params = member.parameterTypes();
for (int i = 0; i < params.count(); ++i) {
int tp = QMetaType::type(params.at(i).constData());
if (tp == QMetaType::Void)
{
qWarning("Don't know how to handle '%s', use qRegisterMetaType to register it.",
params.at(i).constData());
// Check if we have a reference by removing the & from the parameter name
QString argString(params.at(i).constData());
argString.remove("&");
tp = QMetaType::type(argString.toStdString().c_str());
if (tp != QMetaType::Void)
shouldReinterpret << true;
}
else
shouldReinterpret << false;
args << tp;
}
}
and the appendArgs function, where we reinterpret the passed by reference arguments:
void appendArgs(void **a)
{
QList<QVariant> list;
for (int i = 0; i < args.count(); ++i) {
QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
if (shouldReinterpret.at(i))
{
switch (type)
{
case QMetaType::Int:
list << QVariant(type, &(*reinterpret_cast<int*>(a[i + 1])));
break;
// Do the same for other types
}
}
else
list << QVariant(type, a[i + 1]);
}
append(list);
}
Complete code for reference:
class MySignalSpy: public QObject, public QList<QList<QVariant> >
{
public:
MySignalSpy(QObject *obj, const char *aSignal)
{
#ifdef Q_CC_BOR
const int memberOffset = QObject::staticMetaObject.methodCount();
#else
static const int memberOffset = QObject::staticMetaObject.methodCount();
#endif
Q_ASSERT(obj);
Q_ASSERT(aSignal);
if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
return;
}
QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
const QMetaObject *mo = obj->metaObject();
int sigIndex = mo->indexOfMethod(ba.constData());
if (sigIndex < 0) {
qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
return;
}
if (!QMetaObject::connect(obj, sigIndex, this, memberOffset,
Qt::DirectConnection, 0)) {
qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
return;
}
sig = ba;
initArgs(mo->method(sigIndex));
}
inline bool isValid() const { return !sig.isEmpty(); }
inline QByteArray signal() const { return sig; }
int qt_metacall(QMetaObject::Call call, int methodId, void **a)
{
methodId = QObject::qt_metacall(call, methodId, a);
if (methodId < 0)
return methodId;
if (call == QMetaObject::InvokeMetaMethod) {
if (methodId == 0) {
appendArgs(a);
}
--methodId;
}
return methodId;
}
private:
void initArgs(const QMetaMethod &member)
{
QList<QByteArray> params = member.parameterTypes();
for (int i = 0; i < params.count(); ++i) {
int tp = QMetaType::type(params.at(i).constData());
if (tp == QMetaType::Void)
{
qWarning("Don't know how to handle '%s', use qRegisterMetaType to register it.",
params.at(i).constData());
QString argString(params.at(i).constData());
argString.remove("&");
tp = QMetaType::type(argString.toStdString().c_str());
if (tp != QMetaType::Void)
shouldReinterpret << true;
}
else
shouldReinterpret << false;
args << tp;
}
}
void appendArgs(void **a)
{
QList<QVariant> list;
for (int i = 0; i < args.count(); ++i) {
QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
if (shouldReinterpret.at(i))
{
switch (type)
{
case QMetaType::Int:
int k = (*reinterpret_cast<int*>(a[i + 1]));
list << QVariant(type, &k);
break;
}
}
else
list << QVariant(type, a[i + 1]);
}
append(list);
}
// the full, normalized signal name
QByteArray sig;
// holds the QMetaType types for the argument list of the signal
QList<int> args;
// Holds the indexes of the arguments that
QList<bool> shouldReinterpret;
};

Save Configuration Settings to XML file in QT?

I want to Save Configuration Settings to XML file in QT and read it from there ?
What is best approach to do this in QT ?
Any sample/ideas are highly appreciated.
Thanks.
You can register your XML file format with QSettings::registerFormat
Here's a sample implementation of the readXmlFile and writeXmlFile
There is Qt examples for XML.
Also you can use QSettings to store your settings.
Here is my code for exporting settings to XML. What you need is to call ExportSettingsToXml() with opened for writing QFile. The exported file can be imported again with ImportSettingsFromXml().
QString Variant2Text( const QVariant &value )
{
switch (value.type())
{
case QVariant::String:
case QVariant::Int:
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
case QVariant::Double:
case QVariant::Bool:
return value.toString();
}
QByteArray ba;
value.save(QDataStream(&ba, QIODevice::WriteOnly));
return QString(ba.toHex());
}
QVariant Text2Variant( const QString &sText, QVariant::Type nType )
{
switch (nType)
{
case QVariant::String:
case QVariant::Int:
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
case QVariant::Double:
case QVariant::Bool:
{
QVariant var(sText);
if (var.convert(nType))
return var;
return QVariant(sText);
}
return QVariant::fromValue(sText);
}
QVariant value;
value.load(QDataStream(&QByteArray::fromHex(sText.toLatin1()), QIODevice::ReadOnly));
return value;
}
void XmlExportSettingsRecursive(QXmlStreamWriter &writer, QSettings &settings, const QStringList &blackList)
{
QString sGroup = settings.group();
if (!sGroup.isEmpty())
{
writer.writeStartElement("group");
writer.writeAttribute(QStringLiteral("name"), sGroup.mid(sGroup.indexOf(QChar('/')) + 1));
}
// Write child groups first
{
const QStringList &listGroups = settings.childGroups();
foreach (const QString &group, listGroups)
{
settings.beginGroup(group);
XmlExportSettingsRecursive(writer, settings, blackList);
settings.endGroup();
}
}
const QStringList &listKeys = settings.childKeys();
foreach (const QString &key, listKeys)
{
if (blackList.contains(key))
continue;
writer.writeStartElement("key");
writer.writeAttribute(QStringLiteral("name"), key);
const QVariant &value = settings.value(key);
writer.writeAttribute(QStringLiteral("type"), value.typeName());
writer.writeCharacters(Variant2Text(value));
writer.writeEndElement();
}
if (!sGroup.isEmpty())
writer.writeEndElement();
}
bool XmlImportSettingsRecursive(QXmlStreamReader &reader, QSettings &settings)
{
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement())
{
if (reader.name() == QStringLiteral("group"))
{
const QXmlStreamAttributes &attr = reader.attributes();
QString sGroup = attr.value(QStringLiteral("name")).toString();
settings.beginGroup(sGroup);
if (!XmlImportSettingsRecursive(reader, settings))
return false;
settings.endGroup();
}
else if (reader.name() == QStringLiteral("key"))
{
const QXmlStreamAttributes &attr = reader.attributes();
const QString &sKey = attr.value(QStringLiteral("name")).toString();
const QString &sType = attr.value(QStringLiteral("type")).toString();
if (reader.readNext())
{
if (reader.isCharacters() || reader.isEndElement())
{
const QString &sValue = reader.text().toString();
settings.setValue(sKey, Text2Variant(sValue, QVariant::nameToType(sType.toLatin1())));
}
else
continue;
}
}
}
else if (reader.isEndElement() && reader.name() == QStringLiteral("group"))
return true;
}
if (reader.hasError()) {
qDebug()<<reader.errorString();
return false;
}
return true;
}
bool ExportSettingsToXml( QIODevice *device, const QString &settingsGroup = QString(), const QStringList &blackList = QStringList())
{
if (!device || !device->isWritable())
return false;
QSettings settings;
settings.beginGroup(settingsGroup);
QXmlStreamWriter xmlWriter( device );
xmlWriter.setAutoFormatting( true );
xmlWriter.writeStartDocument();
xmlWriter.writeStartElement( QStringLiteral("settings") );
if (!settingsGroup.isEmpty())
{
xmlWriter.writeStartElement(QStringLiteral("group"));
xmlWriter.writeAttribute(QStringLiteral("name"), settingsGroup);
}
XmlExportSettingsRecursive(xmlWriter, settings, blackList);
if (!settingsGroup.isEmpty())
xmlWriter.writeEndElement();
xmlWriter.writeEndDocument();
return true;
}
bool ImportSettingsFromXml( QIODevice *device, const QString &settingsGroup = QString())
{
if (!device || !device->isReadable())
{
qDebug()<<"Trying to import XML settings from non-readable device";
return false;
}
QSettings settings;
settings.beginGroup(settingsGroup);
QXmlStreamReader xmlReader( device );
xmlReader.readNext();
if (!xmlReader.isStartDocument())
{
qDebug()<<"Invalid xml document format";
return false;
}
if (!xmlReader.readNextStartElement())
{
qDebug()<<"Empty settings xml document";
return true;
}
if (xmlReader.name() == QStringLiteral("settings") )
{
if (!XmlImportSettingsRecursive(xmlReader, settings))
return false;
}
else
{
qDebug()<<"Unknown settings format.";
return false;
}
return true;
}
For such things I often use the boost library for C+ and can highly recommend it. It offers serialization for various formats like XML, text, binary etc.
Serialization Tutorial (looks more complicated that it actually is)

Resources