Typica is a free program for professional coffee roasters. https://typica.us
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

webview.w 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. @* Web Views in Typica.
  2. \noindent Typica makes extensive use of web views. All printing is currently
  3. handled by generating HTML with the required information, loading it into a web
  4. view, and using the print capabilities provided there. Reports are provided by
  5. running SQL queries, generating HTML, and displaying the results in a web view.
  6. Even the program'@q'@>s about window is primarily a web view.
  7. In order to simplify the implementation of certain features, we subclass
  8. |QWebView| and provide some additional functionality.
  9. @s QWebElement int
  10. @(webview.h@>=
  11. #include <QWebView>
  12. #include <QFile>
  13. #include <QMessageBox>
  14. #include <QDesktopServices>
  15. #include <QPrinter>
  16. #include <QPrintDialog>
  17. #include <QWebFrame>
  18. #include <QWebElement>
  19. #include <QSettings>
  20. #ifndef TypicaWebViewHeader
  21. #define TypicaWebViewHeader
  22. class TypicaWebView : public QWebView@/
  23. {
  24. @[Q_OBJECT@]@;
  25. public:@/
  26. TypicaWebView();
  27. @[Q_INVOKABLE@,@, void@]@, load(const QString &url);@t\2\2@>@/
  28. @[Q_INVOKABLE@,@, void@]@, print(const QString &printerName = QString());@t\2\2@>@/
  29. @[Q_INVOKABLE@,@, void@]@, setHtml(const QString &html, const QUrl &baseUrl = QUrl());@t\2\2@>@/
  30. @[Q_INVOKABLE@,@, void@]@, setContent(QIODevice *device);@t\2\2@>@/
  31. @[Q_INVOKABLE@,@, QString@]@, saveXml();@t\2\2@>@/
  32. @[Q_INVOKABLE@,@, QWebElement@]@, documentElement();@t\2\2@>@/
  33. @[Q_INVOKABLE@,@, QWebElement@]@, findFirstElement(const QString &selector);@t\2\2@>@/
  34. @[signals@]:@/
  35. void scriptLinkClicked(const QString &link);
  36. @[private slots@]:@/
  37. void linkDelegate(const QUrl &url);
  38. };
  39. #endif
  40. @ The implementation is in a separate file.
  41. @(webview.cpp@>=
  42. #include "webview.h"
  43. @<TypicaWebView implementation@>@;
  44. @ In the constructor we set up our link delegation policy.
  45. @<TypicaWebView implementation@>=
  46. TypicaWebView::TypicaWebView() : QWebView()
  47. {
  48. page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks);
  49. connect(page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkDelegate(QUrl)));
  50. }
  51. @ When a link is clicked, one of three things may happen. Certain reserved URLs
  52. will trigger an immediate event which is just handled internally without
  53. providing any kind of external notification. URLs that start with
  54. |"typica://script/"| will produce a signal containing the rest of the URL. This
  55. is intended for script code to intercept, interpret, and update the web view
  56. with the requested information. Everything else is passed to |QDesktopServices|
  57. which will defer to system-wide preferences for how to handle different link
  58. and file types.
  59. @<TypicaWebView implementation@>=
  60. void TypicaWebView::linkDelegate(const QUrl &url)
  61. {
  62. if(url.scheme() == "typica")
  63. {
  64. QString address(url.toEncoded());
  65. @<Detect and handle special links@>@;
  66. @<Detect and handle script links@>@;
  67. }
  68. else
  69. {
  70. QDesktopServices::openUrl(url);
  71. }
  72. }
  73. @ Currently the only special link is |"typica://aboutqt"| which brings up
  74. information about the Qt framework. This is used in the About Typica window.
  75. @<Detect and handle special links@>=
  76. if(address == "typica://aboutqt")
  77. {
  78. QMessageBox::aboutQt(this);
  79. return;
  80. }
  81. @ Script links split the link data to simplify interpretation in script code.
  82. @<Detect and handle script links@>=
  83. if(address.startsWith("typica://script/"))
  84. {
  85. emit scriptLinkClicked(address.remove(0, 16));
  86. return;
  87. }
  88. @ There is a limited set of functions that should be available to the host
  89. environment which are not declared as slots or otherwise have some missing
  90. functionality in the base class. In some cases the new functions can be
  91. distinguished by signature.
  92. @<TypicaWebView implementation@>=
  93. void TypicaWebView::load(const QString &url)
  94. {
  95. QWebView::load(QUrl(url));
  96. }
  97. void TypicaWebView::setHtml(const QString &html, const QUrl &baseUrl)
  98. {
  99. QWebView::setHtml(html, baseUrl);
  100. }
  101. void TypicaWebView::setContent(QIODevice *device)
  102. {
  103. QSettings settings;
  104. device->reset();
  105. QByteArray content = device->readAll();
  106. QUrl baseDir = QUrl("file://" + settings.value("config").toString() + "/");
  107. QWebView::setContent(content, "application/xhtml+xml", baseDir);
  108. }
  109. QString TypicaWebView::saveXml()
  110. {
  111. return page()->currentFrame()->documentElement().toOuterXml();
  112. }
  113. @ Print functionality has been extended to allow an optional argument. If the
  114. name of a printer is passed in, the print dialog will be bypassed. Note that
  115. when bypassing the print dialog, page margins are set to 0. This is intentional
  116. as the use case is for printing to more specialized printers where default page
  117. margins are not appropriate and CSS can be customized to ensure that all
  118. information fits in the printable area with the assumption of no margin.
  119. @<TypicaWebView implementation@>=
  120. void TypicaWebView::print(const QString &printerName)
  121. {
  122. QPrinter *printer = new QPrinter(QPrinter::HighResolution);
  123. if(!printerName.isEmpty())
  124. {
  125. printer->setPrinterName(printerName);
  126. printer->setFullPage(true);
  127. QWebView::print(printer);
  128. return;
  129. }
  130. QPrintDialog printDialog(printer, NULL);
  131. if(printDialog.exec() == QDialog::Accepted)
  132. {
  133. QWebView::print(printer);
  134. }
  135. }
  136. @ Web views are exposed to the host environment in the usual manner.
  137. @<Set up the scripting engine@>=
  138. constructor = engine->newFunction(constructWebView);
  139. value = engine->newQMetaObject(&TypicaWebView::staticMetaObject, constructor);
  140. engine->globalObject().setProperty("WebView", value);
  141. @ Now that |QWebView| is subclassed, all of the features that we need should
  142. be available through the meta-object system automatically.
  143. @<Functions for scripting@>=
  144. QScriptValue constructWebView(QScriptContext *, QScriptEngine *engine)
  145. {
  146. QScriptValue object = engine->newQObject(new TypicaWebView);
  147. setQWebViewProperties(object, engine);
  148. return object;
  149. }
  150. void setQWebViewProperties(QScriptValue value, QScriptEngine *engine)
  151. {
  152. setQWidgetProperties(value, engine);
  153. }
  154. @ It is also possible to create these web views from the XML portion of the
  155. configuration document. A function is provided to add a new view to a box
  156. layout.
  157. @<Functions for scripting@>=
  158. void addWebViewToLayout(QDomElement element, QStack<QWidget *> *,
  159. QStack<QLayout *> *layoutStack)
  160. {
  161. TypicaWebView *view = new TypicaWebView;
  162. int stretch = 0;
  163. if(element.hasAttribute("id"))
  164. {
  165. view->setObjectName(element.attribute("id"));
  166. }
  167. if(element.hasAttribute("stretch"))
  168. {
  169. stretch = element.attribute("stretch").toInt();
  170. }
  171. QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
  172. layout->addWidget(view, stretch);
  173. }
  174. @ Prototypes must be provided for these functions.
  175. @<Function prototypes for scripting@>=
  176. QScriptValue constructWebView(QScriptContext *context, QScriptEngine *engine);
  177. void setQWebViewProperties(QScriptValue value, QScriptEngine *engine);
  178. void addWebViewToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
  179. QStack<QLayout *> *layoutStack);
  180. @ Finally, we must include our new header in |"typica.cpp"|.
  181. @<Header files to include@>=
  182. #include "webview.h"
  183. @* Web View DOM Access.
  184. \noindent Two methods in |TypicaWebView| provide access to the DOM for the
  185. currently displayed page. These are simple wrappers. The |QWebElement| is also
  186. @<TypicaWebView implementation@>=
  187. QWebElement TypicaWebView::documentElement()
  188. {
  189. return page()->mainFrame()->documentElement();
  190. }
  191. QWebElement TypicaWebView::findFirstElement(const QString &selector)
  192. {
  193. return page()->mainFrame()->findFirstElement(selector);
  194. }
  195. @ In order to call these methods we need to be able to convert a |QWebElement|
  196. to and from a |QScriptValue|.
  197. @<Function prototypes for scripting@>=
  198. QScriptValue QWebElement_toScriptValue(QScriptEngine *engine, const QWebElement &element);
  199. void QWebElement_fromScriptValue(const QScriptValue &value, QWebElement &element);
  200. @ The implementation simply packs these in a variant.
  201. @<Functions for scripting@>=
  202. QScriptValue QWebElement_toScriptValue(QScriptEngine *engine, const QWebElement &element)
  203. {
  204. QVariant var;
  205. var.setValue(element);
  206. QScriptValue object = engine->newVariant(var);
  207. return object;
  208. }
  209. void QWebElement_fromScriptValue(const QScriptValue &value, QWebElement &element)
  210. {
  211. element = value.toVariant().@[value<QWebElement>()@];
  212. }
  213. @ These methods must be registered with the engine.
  214. @<Set up the scripting engine@>=
  215. qScriptRegisterMetaType(engine, QWebElement_toScriptValue, QWebElement_fromScriptValue);
  216. @ As |QWebElement| by itself is not well suited for scripting, we must provide
  217. a way to attach useful properties to the returned object. We do this with a
  218. simple wrapper class.
  219. @(webelement.h@>=
  220. #include <QWebElement>
  221. #include <QObject>
  222. #ifndef TypicaWebElementHeader
  223. #define TypicaWebElementHeader
  224. class TypicaWebElement : public QObject@/
  225. {@/
  226. @[Q_OBJECT@]@;
  227. public:@/
  228. TypicaWebElement(QWebElement element);
  229. @[Q_INVOKABLE@,@, void@]@, appendInside(const QString &markup);@t\2\2@>@/
  230. @[Q_INVOKABLE@,@, void@]@, appendOutside(const QString &markup);@t\2\2@>@/
  231. @[Q_INVOKABLE@,@, void@]@, prependInside(const QString &markup);@t\2\2@>@/
  232. @[Q_INVOKABLE@,@, void@]@, prependOutside(const QString &markup);@t\2\2@>@/
  233. @[Q_INVOKABLE@,@, void@]@, removeFromDocument();@t\2\2@>@/
  234. @[Q_INVOKABLE@,@, void@]@, replace(const QString &markup);@t\2\2@>@/
  235. @[Q_INVOKABLE@,@, void@]@, setInnerXml(const QString &markup);@t\2\2@>@/
  236. @[Q_INVOKABLE@,@, void@]@, setOuterXml(const QString &markup);@t\2\2@>@/
  237. @[Q_INVOKABLE@,@, void@]@, setPlainText(const QString &text);@t\2\2@>@/
  238. private:@/
  239. QWebElement e;@/
  240. };
  241. #endif
  242. @ A constructor is required for creating this wrapper for the host environment.
  243. @<Function prototypes for scripting@>=
  244. QScriptValue constructWebElement(QScriptContext *context,
  245. QScriptEngine *engine);
  246. @ The script engine is informed of this function.
  247. @<Set up the scripting engine@>=
  248. constructor = engine->newFunction(constructWebElement);
  249. engine->globalObject().setProperty("WebElement", constructor);
  250. @ A specialization of the |argument()| template method is required to
  251. pass the |QWebElement| through to the wrapper constructor.
  252. @<Functions for scripting@>=
  253. template<> QWebElement argument(int arg, QScriptContext *context)
  254. {
  255. return qscriptvalue_cast<QWebElement>(context->argument(arg));
  256. }
  257. @ Our wrapper constructor takes a single argument which is the |QWebElement| to
  258. wrap.
  259. @<Functions for scripting@>=
  260. QScriptValue constructWebElement(QScriptContext *context,
  261. QScriptEngine *engine)
  262. {
  263. QWebElement element = argument<QWebElement>(0, context);
  264. QScriptValue object = engine->newQObject(new TypicaWebElement(element));
  265. return object;
  266. }
  267. @ The |TypicaWebElement| constructor just keeps a copy of the element passed as
  268. an argument.
  269. @<TypicaWebElement implementation@>=
  270. TypicaWebElement::TypicaWebElement(QWebElement element) : e(element)
  271. {
  272. /* Nothing needs to be done here. */
  273. }
  274. @ Everything else is a simple wrapper around the equivalent |QWebElement|
  275. method.
  276. @<TypicaWebElement implementation@>=
  277. void TypicaWebElement::appendInside(const QString &markup)
  278. {
  279. e.appendInside(markup);
  280. }
  281. void TypicaWebElement::appendOutside(const QString &markup)
  282. {
  283. e.appendOutside(markup);
  284. }
  285. void TypicaWebElement::prependInside(const QString &markup)
  286. {
  287. e.prependInside(markup);
  288. }
  289. void TypicaWebElement::prependOutside(const QString &markup)
  290. {
  291. e.prependOutside(markup);
  292. }
  293. void TypicaWebElement::removeFromDocument()
  294. {
  295. e.removeFromDocument();
  296. }
  297. void TypicaWebElement::replace(const QString &markup)
  298. {
  299. e.replace(markup);
  300. }
  301. void TypicaWebElement::setInnerXml(const QString &markup)
  302. {
  303. e.setInnerXml(markup);
  304. }
  305. void TypicaWebElement::setOuterXml(const QString &markup)
  306. {
  307. e.setOuterXml(markup);
  308. }
  309. void TypicaWebElement::setPlainText(const QString &text)
  310. {
  311. e.setPlainText(text);
  312. }
  313. @ Implementation is in a separate file.
  314. @(webelement.cpp@>=
  315. #include "webelement.h"
  316. @<TypicaWebElement implementation@>@;
  317. @ Another header is required.
  318. @<Header files to include@>=
  319. #include "webelement.h"