@* Web Views in Typica. \noindent Typica makes extensive use of web views. All printing is currently handled by generating HTML with the required information, loading it into a web view, and using the print capabilities provided there. Reports are provided by running SQL queries, generating HTML, and displaying the results in a web view. Even the program'@q'@>s about window is primarily a web view. In order to simplify the implementation of certain features, we subclass |QWebView| and provide some additional functionality. @s QWebElement int @(webview.h@>= #include #include #include #include #include #include #include #include #include #ifndef TypicaWebViewHeader #define TypicaWebViewHeader class TypicaWebView : public QWebView@/ { @[Q_OBJECT@]@; public:@/ TypicaWebView(); @[Q_INVOKABLE@,@, void@]@, load(const QString &url);@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, print(const QString &printerName = QString());@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, setHtml(const QString &html, const QUrl &baseUrl = QUrl());@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, setContent(QIODevice *device);@t\2\2@>@/ @[Q_INVOKABLE@,@, QString@]@, saveXml();@t\2\2@>@/ @[Q_INVOKABLE@,@, QWebElement@]@, documentElement();@t\2\2@>@/ @[Q_INVOKABLE@,@, QWebElement@]@, findFirstElement(const QString &selector);@t\2\2@>@/ @[signals@]:@/ void scriptLinkClicked(const QString &link); @[private slots@]:@/ void linkDelegate(const QUrl &url); }; #endif @ The implementation is in a separate file. @(webview.cpp@>= #include "webview.h" @@; @ In the constructor we set up our link delegation policy. @= TypicaWebView::TypicaWebView() : QWebView() { page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); connect(page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkDelegate(QUrl))); } @ When a link is clicked, one of three things may happen. Certain reserved URLs will trigger an immediate event which is just handled internally without providing any kind of external notification. URLs that start with |"typica://script/"| will produce a signal containing the rest of the URL. This is intended for script code to intercept, interpret, and update the web view with the requested information. Everything else is passed to |QDesktopServices| which will defer to system-wide preferences for how to handle different link and file types. @= void TypicaWebView::linkDelegate(const QUrl &url) { if(url.scheme() == "typica") { QString address(url.toEncoded()); @@; @@; } else { QDesktopServices::openUrl(url); } } @ Currently the only special link is |"typica://aboutqt"| which brings up information about the Qt framework. This is used in the About Typica window. @= if(address == "typica://aboutqt") { QMessageBox::aboutQt(this); return; } @ Script links split the link data to simplify interpretation in script code. @= if(address.startsWith("typica://script/")) { emit scriptLinkClicked(address.remove(0, 16)); return; } @ There is a limited set of functions that should be available to the host environment which are not declared as slots or otherwise have some missing functionality in the base class. In some cases the new functions can be distinguished by signature. @= void TypicaWebView::load(const QString &url) { QWebView::load(QUrl(url)); } void TypicaWebView::setHtml(const QString &html, const QUrl &baseUrl) { QWebView::setHtml(html, baseUrl); } void TypicaWebView::setContent(QIODevice *device) { QSettings settings; device->reset(); QByteArray content = device->readAll(); QUrl baseDir = QUrl("file://" + settings.value("config").toString() + "/"); QWebView::setContent(content, "application/xhtml+xml", baseDir); } QString TypicaWebView::saveXml() { return page()->currentFrame()->documentElement().toOuterXml(); } @ Print functionality has been extended to allow an optional argument. If the name of a printer is passed in, the print dialog will be bypassed. Note that when bypassing the print dialog, page margins are set to 0. This is intentional as the use case is for printing to more specialized printers where default page margins are not appropriate and CSS can be customized to ensure that all information fits in the printable area with the assumption of no margin. @= void TypicaWebView::print(const QString &printerName) { QPrinter *printer = new QPrinter(QPrinter::HighResolution); if(!printerName.isEmpty()) { printer->setPrinterName(printerName); printer->setFullPage(true); QWebView::print(printer); return; } QPrintDialog printDialog(printer, NULL); if(printDialog.exec() == QDialog::Accepted) { QWebView::print(printer); } } @ Web views are exposed to the host environment in the usual manner. @= constructor = engine->newFunction(constructWebView); value = engine->newQMetaObject(&TypicaWebView::staticMetaObject, constructor); engine->globalObject().setProperty("WebView", value); @ Now that |QWebView| is subclassed, all of the features that we need should be available through the meta-object system automatically. @= QScriptValue constructWebView(QScriptContext *, QScriptEngine *engine) { QScriptValue object = engine->newQObject(new TypicaWebView); setQWebViewProperties(object, engine); return object; } void setQWebViewProperties(QScriptValue value, QScriptEngine *engine) { setQWidgetProperties(value, engine); } @ It is also possible to create these web views from the XML portion of the configuration document. A function is provided to add a new view to a box layout. @= void addWebViewToLayout(QDomElement element, QStack *, QStack *layoutStack) { TypicaWebView *view = new TypicaWebView; int stretch = 0; if(element.hasAttribute("id")) { view->setObjectName(element.attribute("id")); } if(element.hasAttribute("stretch")) { stretch = element.attribute("stretch").toInt(); } QBoxLayout *layout = qobject_cast(layoutStack->top()); layout->addWidget(view, stretch); } @ Prototypes must be provided for these functions. @= QScriptValue constructWebView(QScriptContext *context, QScriptEngine *engine); void setQWebViewProperties(QScriptValue value, QScriptEngine *engine); void addWebViewToLayout(QDomElement element, QStack *widgetStack, QStack *layoutStack); @ Finally, we must include our new header in |"typica.cpp"|. @
= #include "webview.h" @* Web View DOM Access. \noindent Two methods in |TypicaWebView| provide access to the DOM for the currently displayed page. These are simple wrappers. The |QWebElement| is also @= QWebElement TypicaWebView::documentElement() { return page()->mainFrame()->documentElement(); } QWebElement TypicaWebView::findFirstElement(const QString &selector) { return page()->mainFrame()->findFirstElement(selector); } @ In order to call these methods we need to be able to convert a |QWebElement| to and from a |QScriptValue|. @= QScriptValue QWebElement_toScriptValue(QScriptEngine *engine, const QWebElement &element); void QWebElement_fromScriptValue(const QScriptValue &value, QWebElement &element); @ The implementation simply packs these in a variant. @= QScriptValue QWebElement_toScriptValue(QScriptEngine *engine, const QWebElement &element) { QVariant var; var.setValue(element); QScriptValue object = engine->newVariant(var); return object; } void QWebElement_fromScriptValue(const QScriptValue &value, QWebElement &element) { element = value.toVariant().@[value()@]; } @ These methods must be registered with the engine. @= qScriptRegisterMetaType(engine, QWebElement_toScriptValue, QWebElement_fromScriptValue); @ As |QWebElement| by itself is not well suited for scripting, we must provide a way to attach useful properties to the returned object. We do this with a simple wrapper class. @(webelement.h@>= #include #include #ifndef TypicaWebElementHeader #define TypicaWebElementHeader class TypicaWebElement : public QObject@/ {@/ @[Q_OBJECT@]@; public:@/ TypicaWebElement(QWebElement element); @[Q_INVOKABLE@,@, void@]@, appendInside(const QString &markup);@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, appendOutside(const QString &markup);@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, prependInside(const QString &markup);@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, prependOutside(const QString &markup);@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, removeFromDocument();@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, replace(const QString &markup);@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, setInnerXml(const QString &markup);@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, setOuterXml(const QString &markup);@t\2\2@>@/ @[Q_INVOKABLE@,@, void@]@, setPlainText(const QString &text);@t\2\2@>@/ private:@/ QWebElement e;@/ }; #endif @ A constructor is required for creating this wrapper for the host environment. @= QScriptValue constructWebElement(QScriptContext *context, QScriptEngine *engine); @ The script engine is informed of this function. @= constructor = engine->newFunction(constructWebElement); engine->globalObject().setProperty("WebElement", constructor); @ A specialization of the |argument()| template method is required to pass the |QWebElement| through to the wrapper constructor. @= template<> QWebElement argument(int arg, QScriptContext *context) { return qscriptvalue_cast(context->argument(arg)); } @ Our wrapper constructor takes a single argument which is the |QWebElement| to wrap. @= QScriptValue constructWebElement(QScriptContext *context, QScriptEngine *engine) { QWebElement element = argument(0, context); QScriptValue object = engine->newQObject(new TypicaWebElement(element)); return object; } @ The |TypicaWebElement| constructor just keeps a copy of the element passed as an argument. @= TypicaWebElement::TypicaWebElement(QWebElement element) : e(element) { /* Nothing needs to be done here. */ } @ Everything else is a simple wrapper around the equivalent |QWebElement| method. @= void TypicaWebElement::appendInside(const QString &markup) { e.appendInside(markup); } void TypicaWebElement::appendOutside(const QString &markup) { e.appendOutside(markup); } void TypicaWebElement::prependInside(const QString &markup) { e.prependInside(markup); } void TypicaWebElement::prependOutside(const QString &markup) { e.prependOutside(markup); } void TypicaWebElement::removeFromDocument() { e.removeFromDocument(); } void TypicaWebElement::replace(const QString &markup) { e.replace(markup); } void TypicaWebElement::setInnerXml(const QString &markup) { e.setInnerXml(markup); } void TypicaWebElement::setOuterXml(const QString &markup) { e.setOuterXml(markup); } void TypicaWebElement::setPlainText(const QString &text) { e.setPlainText(text); } @ Implementation is in a separate file. @(webelement.cpp@>= #include "webelement.h" @@; @ Another header is required. @
= #include "webelement.h"