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

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