aboutsummaryrefslogtreecommitdiffstats
path: root/examples/webenginequick
diff options
context:
space:
mode:
Diffstat (limited to 'examples/webenginequick')
-rw-r--r--examples/webenginequick/nanobrowser/ApplicationRoot.qml40
-rw-r--r--examples/webenginequick/nanobrowser/BrowserDialog.qml27
-rw-r--r--examples/webenginequick/nanobrowser/BrowserWindow.qml817
-rw-r--r--examples/webenginequick/nanobrowser/DownloadView.qml127
-rw-r--r--examples/webenginequick/nanobrowser/FindBar.qml95
-rw-r--r--examples/webenginequick/nanobrowser/FullScreenNotification.qml62
-rw-r--r--examples/webenginequick/nanobrowser/browser.qml16
-rw-r--r--examples/webenginequick/nanobrowser/icons/3rdparty/COPYING1
-rw-r--r--examples/webenginequick/nanobrowser/icons/3rdparty/go-next.pngbin0 -> 930 bytes
-rw-r--r--examples/webenginequick/nanobrowser/icons/3rdparty/go-previous.pngbin0 -> 955 bytes
-rw-r--r--examples/webenginequick/nanobrowser/icons/3rdparty/process-stop.pngbin0 -> 1272 bytes
-rw-r--r--examples/webenginequick/nanobrowser/icons/3rdparty/view-refresh.pngbin0 -> 1364 bytes
-rw-r--r--examples/webenginequick/nanobrowser/nanobrowser.pyproject5
-rw-r--r--examples/webenginequick/nanobrowser/quicknanobrowser.py68
-rw-r--r--examples/webenginequick/nanobrowser/rc_resources.py348
-rw-r--r--examples/webenginequick/nanobrowser/resources.qrc8
16 files changed, 1586 insertions, 28 deletions
diff --git a/examples/webenginequick/nanobrowser/ApplicationRoot.qml b/examples/webenginequick/nanobrowser/ApplicationRoot.qml
new file mode 100644
index 000000000..55c414409
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/ApplicationRoot.qml
@@ -0,0 +1,40 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtWebEngine
+
+QtObject {
+ id: root
+
+ property QtObject defaultProfile: WebEngineProfile {
+ storageName: "Profile"
+ offTheRecord: false
+ }
+
+ property QtObject otrProfile: WebEngineProfile {
+ offTheRecord: true
+ }
+
+ property Component browserWindowComponent: BrowserWindow {
+ applicationRoot: root
+ }
+ property Component browserDialogComponent: BrowserDialog {
+ onClosing: destroy()
+ }
+ function createWindow(profile) {
+ var newWindow = browserWindowComponent.createObject(root);
+ newWindow.currentWebView.profile = profile;
+ profile.downloadRequested.connect(newWindow.onDownloadRequested);
+ return newWindow;
+ }
+ function createDialog(profile) {
+ var newDialog = browserDialogComponent.createObject(root);
+ newDialog.currentWebView.profile = profile;
+ return newDialog;
+ }
+ function load(url) {
+ var browserWindow = createWindow(defaultProfile);
+ browserWindow.currentWebView.url = url;
+ }
+}
diff --git a/examples/webenginequick/nanobrowser/BrowserDialog.qml b/examples/webenginequick/nanobrowser/BrowserDialog.qml
new file mode 100644
index 000000000..7af347ec3
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/BrowserDialog.qml
@@ -0,0 +1,27 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import QtWebEngine
+
+Window {
+ id: window
+ property alias currentWebView: webView
+ flags: Qt.Dialog
+ width: 800
+ height: 600
+ visible: true
+ onClosing: destroy()
+ WebEngineView {
+ id: webView
+ anchors.fill: parent
+
+ onGeometryChangeRequested: function(geometry) {
+ window.x = geometry.x
+ window.y = geometry.y
+ window.width = geometry.width
+ window.height = geometry.height
+ }
+ }
+}
diff --git a/examples/webenginequick/nanobrowser/BrowserWindow.qml b/examples/webenginequick/nanobrowser/BrowserWindow.qml
new file mode 100644
index 000000000..a517c5a51
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/BrowserWindow.qml
@@ -0,0 +1,817 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtCore
+import QtQml
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Window
+import QtWebEngine
+import BrowserUtils
+
+ApplicationWindow {
+ id: browserWindow
+ property QtObject applicationRoot
+ property Item currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
+ property int previousVisibility: Window.Windowed
+ property int createdTabs: 0
+
+ width: 1300
+ height: 900
+ visible: true
+ title: currentWebView && currentWebView.title
+
+ // Make sure the Qt.WindowFullscreenButtonHint is set on OS X.
+ Component.onCompleted: flags = flags | Qt.WindowFullscreenButtonHint
+
+ onCurrentWebViewChanged: {
+ findBar.reset();
+ }
+
+ // When using style "mac", ToolButtons are not supposed to accept focus.
+ property bool platformIsMac: Qt.platform.os == "osx"
+
+ Settings {
+ id : appSettings
+ property alias autoLoadImages: loadImages.checked
+ property alias javaScriptEnabled: javaScriptEnabled.checked
+ property alias errorPageEnabled: errorPageEnabled.checked
+ property alias pluginsEnabled: pluginsEnabled.checked
+ property alias fullScreenSupportEnabled: fullScreenSupportEnabled.checked
+ property alias autoLoadIconsForPage: autoLoadIconsForPage.checked
+ property alias touchIconsEnabled: touchIconsEnabled.checked
+ property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked
+ property alias devToolsEnabled: devToolsEnabled.checked
+ property alias pdfViewerEnabled: pdfViewerEnabled.checked
+ }
+
+ Action {
+ shortcut: "Ctrl+D"
+ onTriggered: {
+ downloadView.visible = !downloadView.visible;
+ }
+ }
+ Action {
+ id: focus
+ shortcut: "Ctrl+L"
+ onTriggered: {
+ addressBar.forceActiveFocus();
+ addressBar.selectAll();
+ }
+ }
+ Action {
+ shortcut: StandardKey.Refresh
+ onTriggered: {
+ if (currentWebView)
+ currentWebView.reload();
+ }
+ }
+ Action {
+ shortcut: StandardKey.AddTab
+ onTriggered: {
+ tabBar.createTab(tabBar.count != 0 ? currentWebView.profile : defaultProfile);
+ addressBar.forceActiveFocus();
+ addressBar.selectAll();
+ }
+ }
+ Action {
+ shortcut: StandardKey.Close
+ onTriggered: {
+ currentWebView.triggerWebAction(WebEngineView.RequestClose);
+ }
+ }
+ Action {
+ shortcut: StandardKey.Quit
+ onTriggered: browserWindow.close()
+ }
+ Action {
+ shortcut: "Escape"
+ onTriggered: {
+ if (currentWebView.state == "FullScreen") {
+ browserWindow.visibility = browserWindow.previousVisibility;
+ fullScreenNotification.hide();
+ currentWebView.triggerWebAction(WebEngineView.ExitFullScreen);
+ }
+
+ if (findBar.visible)
+ findBar.visible = false;
+ }
+ }
+ Action {
+ shortcut: "Ctrl+0"
+ onTriggered: currentWebView.zoomFactor = 1.0
+ }
+ Action {
+ shortcut: StandardKey.ZoomOut
+ onTriggered: currentWebView.zoomFactor -= 0.1
+ }
+ Action {
+ shortcut: StandardKey.ZoomIn
+ onTriggered: currentWebView.zoomFactor += 0.1
+ }
+
+ Action {
+ shortcut: StandardKey.Copy
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.Copy)
+ }
+ Action {
+ shortcut: StandardKey.Cut
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.Cut)
+ }
+ Action {
+ shortcut: StandardKey.Paste
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.Paste)
+ }
+ Action {
+ shortcut: "Shift+"+StandardKey.Paste
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.PasteAndMatchStyle)
+ }
+ Action {
+ shortcut: StandardKey.SelectAll
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.SelectAll)
+ }
+ Action {
+ shortcut: StandardKey.Undo
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.Undo)
+ }
+ Action {
+ shortcut: StandardKey.Redo
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.Redo)
+ }
+ Action {
+ shortcut: StandardKey.Back
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.Back)
+ }
+ Action {
+ shortcut: StandardKey.Forward
+ onTriggered: currentWebView.triggerWebAction(WebEngineView.Forward)
+ }
+ Action {
+ shortcut: StandardKey.Find
+ onTriggered: {
+ if (!findBar.visible)
+ findBar.visible = true;
+ }
+ }
+ Action {
+ shortcut: StandardKey.FindNext
+ onTriggered: findBar.findNext()
+ }
+ Action {
+ shortcut: StandardKey.FindPrevious
+ onTriggered: findBar.findPrevious()
+ }
+
+ menuBar: ToolBar {
+ id: navigationBar
+ RowLayout {
+ anchors.fill: parent
+ ToolButton {
+ enabled: currentWebView && (currentWebView.canGoBack || currentWebView.canGoForward)
+ onClicked: historyMenu.open()
+ text: qsTr("▼")
+ Menu {
+ id: historyMenu
+ Instantiator {
+ model: currentWebView && currentWebView.history.items
+ MenuItem {
+ text: model.title
+ onTriggered: currentWebView.goBackOrForward(model.offset)
+ checkable: !enabled
+ checked: !enabled
+ enabled: model.offset
+ }
+
+ onObjectAdded: function(index, object) {
+ historyMenu.insertItem(index, object)
+ }
+ onObjectRemoved: function(index, object) {
+ historyMenu.removeItem(object)
+ }
+ }
+ }
+ }
+
+ ToolButton {
+ id: backButton
+ icon.source: "qrc:/icons/go-previous.png"
+ onClicked: currentWebView.goBack()
+ enabled: currentWebView && currentWebView.canGoBack
+ activeFocusOnTab: !browserWindow.platformIsMac
+ }
+ ToolButton {
+ id: forwardButton
+ icon.source: "qrc:/icons/go-next.png"
+ onClicked: currentWebView.goForward()
+ enabled: currentWebView && currentWebView.canGoForward
+ activeFocusOnTab: !browserWindow.platformIsMac
+ }
+ ToolButton {
+ id: reloadButton
+ icon.source: currentWebView && currentWebView.loading ? "qrc:/icons/process-stop.png" : "qrc:/icons/view-refresh.png"
+ onClicked: currentWebView && currentWebView.loading ? currentWebView.stop() : currentWebView.reload()
+ activeFocusOnTab: !browserWindow.platformIsMac
+ }
+ TextField {
+ id: addressBar
+ Image {
+ anchors.verticalCenter: addressBar.verticalCenter;
+ x: 5
+ z: 2
+ id: faviconImage
+ width: 16; height: 16
+ sourceSize: Qt.size(width, height)
+ source: currentWebView && currentWebView.icon ? currentWebView.icon : ''
+ }
+ MouseArea {
+ id: textFieldMouseArea
+ acceptedButtons: Qt.RightButton
+ anchors.fill: parent
+ onClicked: {
+ var textSelectionStartPos = addressBar.selectionStart;
+ var textSelectionEndPos = addressBar.selectionEnd;
+ textFieldContextMenu.open();
+ addressBar.select(textSelectionStartPos, textSelectionEndPos);
+ }
+ Menu {
+ id: textFieldContextMenu
+ x: textFieldMouseArea.mouseX
+ y: textFieldMouseArea.mouseY
+ MenuItem {
+ text: qsTr("Cut")
+ onTriggered: addressBar.cut()
+ enabled: addressBar.selectedText.length > 0
+ }
+ MenuItem {
+ text: qsTr("Copy")
+ onTriggered: addressBar.copy()
+ enabled: addressBar.selectedText.length > 0
+ }
+ MenuItem {
+ text: qsTr("Paste")
+ onTriggered: addressBar.paste()
+ enabled: addressBar.canPaste
+ }
+ MenuItem {
+ text: qsTr("Delete")
+ onTriggered: addressBar.text = qsTr("")
+ enabled: addressBar.selectedText.length > 0
+ }
+ MenuSeparator {}
+ MenuItem {
+ text: qsTr("Select All")
+ onTriggered: addressBar.selectAll()
+ enabled: addressBar.text.length > 0
+ }
+ }
+ }
+ leftPadding: 26
+ focus: true
+ Layout.fillWidth: true
+ Binding on text {
+ when: currentWebView
+ value: currentWebView.url
+ }
+ onAccepted: currentWebView.url = Utils.fromUserInput(text)
+ selectByMouse: true
+ }
+ ToolButton {
+ id: settingsMenuButton
+ text: qsTr("⋮")
+ onClicked: settingsMenu.open()
+ Menu {
+ id: settingsMenu
+ y: settingsMenuButton.height
+ MenuItem {
+ id: loadImages
+ text: "Autoload images"
+ checkable: true
+ checked: WebEngine.settings.autoLoadImages
+ }
+ MenuItem {
+ id: javaScriptEnabled
+ text: "JavaScript On"
+ checkable: true
+ checked: WebEngine.settings.javascriptEnabled
+ }
+ MenuItem {
+ id: errorPageEnabled
+ text: "ErrorPage On"
+ checkable: true
+ checked: WebEngine.settings.errorPageEnabled
+ }
+ MenuItem {
+ id: pluginsEnabled
+ text: "Plugins On"
+ checkable: true
+ checked: true
+ }
+ MenuItem {
+ id: fullScreenSupportEnabled
+ text: "FullScreen On"
+ checkable: true
+ checked: WebEngine.settings.fullScreenSupportEnabled
+ }
+ MenuItem {
+ id: offTheRecordEnabled
+ text: "Off The Record"
+ checkable: true
+ checked: currentWebView && currentWebView.profile === otrProfile
+ onToggled: function(checked) {
+ if (currentWebView) {
+ currentWebView.profile = checked ? otrProfile : defaultProfile;
+ }
+ }
+ }
+ MenuItem {
+ id: httpDiskCacheEnabled
+ text: "HTTP Disk Cache"
+ checkable: currentWebView && !currentWebView.profile.offTheRecord
+ checked: currentWebView && (currentWebView.profile.httpCacheType === WebEngineProfile.DiskHttpCache)
+ onToggled: function(checked) {
+ if (currentWebView) {
+ currentWebView.profile.httpCacheType = checked ? WebEngineProfile.DiskHttpCache : WebEngineProfile.MemoryHttpCache;
+ }
+ }
+ }
+ MenuItem {
+ id: autoLoadIconsForPage
+ text: "Icons On"
+ checkable: true
+ checked: WebEngine.settings.autoLoadIconsForPage
+ }
+ MenuItem {
+ id: touchIconsEnabled
+ text: "Touch Icons On"
+ checkable: true
+ checked: WebEngine.settings.touchIconsEnabled
+ enabled: autoLoadIconsForPage.checked
+ }
+ MenuItem {
+ id: webRTCPublicInterfacesOnly
+ text: "WebRTC Public Interfaces Only"
+ checkable: true
+ checked: WebEngine.settings.webRTCPublicInterfacesOnly
+ }
+ MenuItem {
+ id: devToolsEnabled
+ text: "Open DevTools"
+ checkable: true
+ checked: false
+ }
+ MenuItem {
+ id: pdfViewerEnabled
+ text: "PDF viewer enabled"
+ checkable: true
+ checked: WebEngine.settings.pdfViewerEnabled
+ }
+ }
+ }
+ }
+ ProgressBar {
+ id: progressBar
+ height: 3
+ anchors {
+ left: parent.left
+ top: parent.bottom
+ right: parent.right
+ leftMargin: parent.leftMargin
+ rightMargin: parent.rightMargin
+ }
+ background: Item {}
+ z: -2
+ from: 0
+ to: 100
+ value: (currentWebView && currentWebView.loadProgress < 100) ? currentWebView.loadProgress : 0
+ }
+ }
+
+ StackLayout {
+ id: tabLayout
+ currentIndex: tabBar.currentIndex
+
+ anchors.top: tabBar.bottom
+ anchors.bottom: devToolsView.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+
+ Component {
+ id: tabButtonComponent
+
+ TabButton {
+ property color frameColor: "#999"
+ property color fillColor: "#eee"
+ property color nonSelectedColor: "#ddd"
+ property string tabTitle: "New Tab"
+
+ id: tabButton
+ contentItem: Rectangle {
+ id: tabRectangle
+ color: tabButton.down ? fillColor : nonSelectedColor
+ border.width: 1
+ border.color: frameColor
+ implicitWidth: Math.max(text.width + 30, 80)
+ implicitHeight: Math.max(text.height + 10, 20)
+ Rectangle { height: 1 ; width: parent.width ; color: frameColor}
+ Rectangle { height: parent.height ; width: 1; color: frameColor}
+ Rectangle { x: parent.width - 2; height: parent.height ; width: 1; color: frameColor}
+ Text {
+ id: text
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: 6
+ text: tabButton.tabTitle
+ elide: Text.ElideRight
+ color: tabButton.down ? "black" : frameColor
+ width: parent.width - button.background.width
+ }
+ Button {
+ id: button
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.rightMargin: 4
+ height: 12
+ background: Rectangle {
+ implicitWidth: 12
+ implicitHeight: 12
+ color: button.hovered ? "#ccc" : tabRectangle.color
+ Text {text: "x"; anchors.centerIn: parent; color: "gray"}
+ }
+ onClicked: tabButton.closeTab()
+ }
+ }
+
+ onClicked: addressBar.text = tabLayout.itemAt(TabBar.index).url;
+ function closeTab() {
+ tabBar.removeView(TabBar.index);
+ }
+ }
+ }
+
+ TabBar {
+ id: tabBar
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ Component.onCompleted: createTab(defaultProfile)
+
+ function createTab(profile, focusOnNewTab = true, url = undefined) {
+ var webview = tabComponent.createObject(tabLayout, {profile: profile});
+ var newTabButton = tabButtonComponent.createObject(tabBar, {tabTitle: Qt.binding(function () { return webview.title; })});
+ tabBar.addItem(newTabButton);
+ if (focusOnNewTab) {
+ tabBar.setCurrentIndex(tabBar.count - 1);
+ }
+ if (url !== undefined) {
+ webview.url = url;
+ }
+ return webview;
+ }
+
+ function removeView(index) {
+ tabBar.removeItem(index);
+ if (tabBar.count > 1) {
+ tabBar.removeItem(tabBar.itemAt(index));
+ tabLayout.children[index].destroy();
+ } else {
+ browserWindow.close();
+ }
+ }
+
+ Component {
+ id: tabComponent
+ WebEngineView {
+ id: webEngineView
+ focus: true
+
+ onLinkHovered: function(hoveredUrl) {
+ if (hoveredUrl == "")
+ hideStatusText.start();
+ else {
+ statusText.text = hoveredUrl;
+ statusBubble.visible = true;
+ hideStatusText.stop();
+ }
+ }
+
+ states: [
+ State {
+ name: "FullScreen"
+ PropertyChanges {
+ target: tabBar
+ visible: false
+ height: 0
+ }
+ PropertyChanges {
+ target: navigationBar
+ visible: false
+ }
+ }
+ ]
+ settings.localContentCanAccessRemoteUrls: true
+ settings.localContentCanAccessFileUrls: false
+ settings.autoLoadImages: appSettings.autoLoadImages
+ settings.javascriptEnabled: appSettings.javaScriptEnabled
+ settings.errorPageEnabled: appSettings.errorPageEnabled
+ settings.pluginsEnabled: appSettings.pluginsEnabled
+ settings.fullScreenSupportEnabled: appSettings.fullScreenSupportEnabled
+ settings.autoLoadIconsForPage: appSettings.autoLoadIconsForPage
+ settings.touchIconsEnabled: appSettings.touchIconsEnabled
+ settings.webRTCPublicInterfacesOnly: appSettings.webRTCPublicInterfacesOnly
+ settings.pdfViewerEnabled: appSettings.pdfViewerEnabled
+
+ onCertificateError: function(error) {
+ error.defer();
+ sslDialog.enqueue(error);
+ }
+
+ onNewWindowRequested: function(request) {
+ if (!request.userInitiated)
+ console.warn("Blocked a popup window.");
+ else if (request.destination === WebEngineNewWindowRequest.InNewTab) {
+ var tab = tabBar.createTab(currentWebView.profile, true, request.requestedUrl);
+ tab.acceptAsNewWindow(request);
+ } else if (request.destination === WebEngineNewWindowRequest.InNewBackgroundTab) {
+ var backgroundTab = tabBar.createTab(currentWebView.profile, false);
+ backgroundTab.acceptAsNewWindow(request);
+ } else if (request.destination === WebEngineNewWindowRequest.InNewDialog) {
+ var dialog = applicationRoot.createDialog(currentWebView.profile);
+ dialog.currentWebView.acceptAsNewWindow(request);
+ } else {
+ var window = applicationRoot.createWindow(currentWebView.profile);
+ window.currentWebView.acceptAsNewWindow(request);
+ }
+ }
+
+ onFullScreenRequested: function(request) {
+ if (request.toggleOn) {
+ webEngineView.state = "FullScreen";
+ browserWindow.previousVisibility = browserWindow.visibility;
+ browserWindow.showFullScreen();
+ fullScreenNotification.show();
+ } else {
+ webEngineView.state = "";
+ browserWindow.visibility = browserWindow.previousVisibility;
+ fullScreenNotification.hide();
+ }
+ request.accept();
+ }
+
+ onRegisterProtocolHandlerRequested: function(request) {
+ console.log("accepting registerProtocolHandler request for "
+ + request.scheme + " from " + request.origin);
+ request.accept();
+ }
+
+ onRenderProcessTerminated: function(terminationStatus, exitCode) {
+ var status = "";
+ switch (terminationStatus) {
+ case WebEngineView.NormalTerminationStatus:
+ status = "(normal exit)";
+ break;
+ case WebEngineView.AbnormalTerminationStatus:
+ status = "(abnormal exit)";
+ break;
+ case WebEngineView.CrashedTerminationStatus:
+ status = "(crashed)";
+ break;
+ case WebEngineView.KilledTerminationStatus:
+ status = "(killed)";
+ break;
+ }
+
+ print("Render process exited with code " + exitCode + " " + status);
+ reloadTimer.running = true;
+ }
+
+ onSelectClientCertificate: function(selection) {
+ selection.certificates[0].select();
+ }
+
+ onFindTextFinished: function(result) {
+ if (!findBar.visible)
+ findBar.visible = true;
+
+ findBar.numberOfMatches = result.numberOfMatches;
+ findBar.activeMatch = result.activeMatch;
+ }
+
+ onLoadingChanged: function(loadRequest) {
+ if (loadRequest.status == WebEngineView.LoadStartedStatus)
+ findBar.reset();
+ }
+
+ onFeaturePermissionRequested: function(securityOrigin, feature) {
+ featurePermissionDialog.securityOrigin = securityOrigin;
+ featurePermissionDialog.feature = feature;
+ featurePermissionDialog.visible = true;
+ }
+
+ Timer {
+ id: reloadTimer
+ interval: 0
+ running: false
+ repeat: false
+ onTriggered: currentWebView.reload()
+ }
+ }
+ }
+ }
+ WebEngineView {
+ id: devToolsView
+ visible: devToolsEnabled.checked
+ height: visible ? 400 : 0
+ inspectedView: visible && tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ onNewWindowRequested: function(request) {
+ var tab = tabBar.createTab(currentWebView.profile);
+ request.openIn(tab);
+ }
+
+ Timer {
+ id: hideTimer
+ interval: 0
+ running: false
+ repeat: false
+ onTriggered: devToolsEnabled.checked = false
+ }
+ onWindowCloseRequested: function(request) {
+ // Delay hiding for keep the inspectedView set to receive the ACK message of close.
+ hideTimer.running = true;
+ }
+ }
+ Dialog {
+ id: sslDialog
+ anchors.centerIn: parent
+ contentWidth: Math.max(mainTextForSSLDialog.width, detailedTextForSSLDialog.width)
+ contentHeight: mainTextForSSLDialog.height + detailedTextForSSLDialog.height
+ property var certErrors: []
+ // fixme: icon!
+ // icon: StandardIcon.Warning
+ standardButtons: Dialog.No | Dialog.Yes
+ title: "Server's certificate not trusted"
+ contentItem: Item {
+ Label {
+ id: mainTextForSSLDialog
+ text: "Do you wish to continue?"
+ }
+ Text {
+ id: detailedTextForSSLDialog
+ anchors.top: mainTextForSSLDialog.bottom
+ text: "If you wish so, you may continue with an unverified certificate.\n" +
+ "Accepting an unverified certificate means\n" +
+ "you may not be connected with the host you tried to connect to.\n" +
+ "Do you wish to override the security check and continue?"
+ }
+ }
+
+ onAccepted: {
+ certErrors.shift().acceptCertificate();
+ presentError();
+ }
+ onRejected: reject()
+
+ function reject(){
+ certErrors.shift().rejectCertificate();
+ presentError();
+ }
+ function enqueue(error){
+ certErrors.push(error);
+ presentError();
+ }
+ function presentError(){
+ visible = certErrors.length > 0
+ }
+ }
+ Dialog {
+ id: featurePermissionDialog
+ anchors.centerIn: parent
+ width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2
+ contentWidth: mainTextForPermissionDialog.width
+ contentHeight: mainTextForPermissionDialog.height
+ standardButtons: Dialog.No | Dialog.Yes
+ title: "Permission Request"
+
+ property var feature;
+ property url securityOrigin;
+
+ contentItem: Item {
+ Label {
+ id: mainTextForPermissionDialog
+ text: featurePermissionDialog.questionForFeature()
+ }
+ }
+
+ onAccepted: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, true)
+ onRejected: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, false)
+ onVisibleChanged: {
+ if (visible)
+ width = contentWidth + 20;
+ }
+
+ function questionForFeature() {
+ var question = "Allow " + securityOrigin + " to "
+
+ switch (feature) {
+ case WebEngineView.Geolocation:
+ question += "access your location information?";
+ break;
+ case WebEngineView.MediaAudioCapture:
+ question += "access your microphone?";
+ break;
+ case WebEngineView.MediaVideoCapture:
+ question += "access your webcam?";
+ break;
+ case WebEngineView.MediaVideoCapture:
+ question += "access your microphone and webcam?";
+ break;
+ case WebEngineView.MouseLock:
+ question += "lock your mouse cursor?";
+ break;
+ case WebEngineView.DesktopVideoCapture:
+ question += "capture video of your desktop?";
+ break;
+ case WebEngineView.DesktopAudioVideoCapture:
+ question += "capture audio and video of your desktop?";
+ break;
+ case WebEngineView.Notifications:
+ question += "show notification on your desktop?";
+ break;
+ default:
+ question += "access unknown or unsupported feature [" + feature + "] ?";
+ break;
+ }
+
+ return question;
+ }
+ }
+
+ FullScreenNotification {
+ id: fullScreenNotification
+ }
+
+ DownloadView {
+ id: downloadView
+ visible: false
+ anchors.fill: parent
+ }
+
+ function onDownloadRequested(download) {
+ downloadView.visible = true;
+ downloadView.append(download);
+ download.accept();
+ }
+
+ FindBar {
+ id: findBar
+ visible: false
+ anchors.right: parent.right
+ anchors.rightMargin: 10
+ anchors.top: parent.top
+
+ onFindNext: {
+ if (text)
+ currentWebView && currentWebView.findText(text);
+ else if (!visible)
+ visible = true;
+ }
+ onFindPrevious: {
+ if (text)
+ currentWebView && currentWebView.findText(text, WebEngineView.FindBackward);
+ else if (!visible)
+ visible = true;
+ }
+ }
+
+
+ Rectangle {
+ id: statusBubble
+ color: "oldlace"
+ property int padding: 8
+ visible: false
+
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ width: statusText.paintedWidth + padding
+ height: statusText.paintedHeight + padding
+
+ Text {
+ id: statusText
+ anchors.centerIn: statusBubble
+ elide: Qt.ElideMiddle
+
+ Timer {
+ id: hideStatusText
+ interval: 750
+ onTriggered: {
+ statusText.text = "";
+ statusBubble.visible = false;
+ }
+ }
+ }
+ }
+}
diff --git a/examples/webenginequick/nanobrowser/DownloadView.qml b/examples/webenginequick/nanobrowser/DownloadView.qml
new file mode 100644
index 000000000..e16647cdb
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/DownloadView.qml
@@ -0,0 +1,127 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtWebEngine
+import QtQuick.Layouts
+
+Rectangle {
+ id: downloadView
+ color: "lightgray"
+
+ ListModel {
+ id: downloadModel
+ property var downloads: []
+ }
+
+ function append(download) {
+ downloadModel.append(download);
+ downloadModel.downloads.push(download);
+ }
+
+ Component {
+ id: downloadItemDelegate
+
+ Rectangle {
+ width: listView.width
+ height: childrenRect.height
+ anchors.margins: 10
+ radius: 3
+ color: "transparent"
+ border.color: "black"
+ Rectangle {
+ id: progressBar
+
+ property real progress: downloadModel.downloads[index]
+ ? downloadModel.downloads[index].receivedBytes / downloadModel.downloads[index].totalBytes : 0
+
+ radius: 3
+ color: width == listView.width ? "green" : "#2b74c7"
+ width: listView.width * progress
+ height: cancelButton.height
+
+ Behavior on width {
+ SmoothedAnimation { duration: 100 }
+ }
+ }
+ Rectangle {
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: 20
+ }
+ Label {
+ id: label
+ text: downloadModel.downloads[index] ? downloadModel.downloads[index].downloadDirectory + "/" + downloadModel.downloads[index].downloadFileName : qsTr("")
+ anchors {
+ verticalCenter: cancelButton.verticalCenter
+ left: parent.left
+ right: cancelButton.left
+ }
+ }
+ Button {
+ id: cancelButton
+ anchors.right: parent.right
+ icon.source: "qrc:/icons/process-stop.png"
+ onClicked: {
+ var download = downloadModel.downloads[index];
+
+ download.cancel();
+
+ downloadModel.downloads = downloadModel.downloads.filter(function (el) {
+ return el.id !== download.id;
+ });
+ downloadModel.remove(index);
+ }
+ }
+ }
+ }
+
+ }
+ ListView {
+ id: listView
+ anchors {
+ topMargin: 10
+ top: parent.top
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ }
+ width: parent.width - 20
+ spacing: 5
+
+ model: downloadModel
+ delegate: downloadItemDelegate
+
+ Text {
+ visible: !listView.count
+ horizontalAlignment: Text.AlignHCenter
+ height: 30
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+ font.pixelSize: 20
+ text: "No active downloads."
+ }
+
+ Rectangle {
+ color: "gray"
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: 30
+ Button {
+ id: okButton
+ text: "OK"
+ anchors.centerIn: parent
+ onClicked: {
+ downloadView.visible = false;
+ }
+ }
+ }
+ }
+}
diff --git a/examples/webenginequick/nanobrowser/FindBar.qml b/examples/webenginequick/nanobrowser/FindBar.qml
new file mode 100644
index 000000000..4d130a22b
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/FindBar.qml
@@ -0,0 +1,95 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Rectangle {
+ id: root
+
+ property int numberOfMatches: 0
+ property int activeMatch: 0
+ property alias text: findTextField.text
+
+ function reset() {
+ numberOfMatches = 0;
+ activeMatch = 0;
+ visible = false;
+ }
+
+ signal findNext()
+ signal findPrevious()
+
+ width: 250
+ height: 35
+ radius: 2
+
+ border.width: 1
+ border.color: "black"
+ color: "white"
+
+ onVisibleChanged: {
+ if (visible)
+ findTextField.forceActiveFocus();
+ }
+
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.topMargin: 5
+ anchors.bottomMargin: 5
+ anchors.leftMargin: 10
+ anchors.rightMargin: 10
+
+ spacing: 5
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ TextField {
+ id: findTextField
+ anchors.fill: parent
+ background: Rectangle {
+ color: "transparent"
+ }
+
+ onAccepted: root.findNext()
+ onTextChanged: root.findNext()
+ onActiveFocusChanged: activeFocus ? selectAll() : deselect()
+ }
+ }
+
+ Label {
+ text: activeMatch + "/" + numberOfMatches
+ visible: findTextField.text != ""
+ }
+
+ Rectangle {
+ border.width: 1
+ border.color: "#ddd"
+ width: 2
+ height: parent.height
+ anchors.topMargin: 5
+ anchors.bottomMargin: 5
+ }
+
+ ToolButton {
+ text: "<"
+ enabled: numberOfMatches > 0
+ onClicked: root.findPrevious()
+ }
+
+ ToolButton {
+ text: ">"
+ enabled: numberOfMatches > 0
+ onClicked: root.findNext()
+ }
+
+ ToolButton {
+ text: "x"
+ onClicked: root.visible = false
+ }
+ }
+}
diff --git a/examples/webenginequick/nanobrowser/FullScreenNotification.qml b/examples/webenginequick/nanobrowser/FullScreenNotification.qml
new file mode 100644
index 000000000..779406432
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/FullScreenNotification.qml
@@ -0,0 +1,62 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Rectangle {
+ id: fullScreenNotification
+ width: 500
+ height: 40
+ color: "white"
+ radius: 7
+
+ visible: false
+ opacity: 0
+
+ function show() {
+ visible = true;
+ opacity = 1;
+ reset.start();
+ }
+
+ function hide() {
+ reset.stop();
+ opacity = 0;
+ }
+
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 750
+ onStopped: {
+ if (opacity == 0)
+ visible = false;
+ }
+ }
+ }
+
+ Timer {
+ id: reset
+ interval: 5000
+ onTriggered: hide()
+ }
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: 125
+
+ Text {
+ id: message
+ width: parent.width
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+
+ wrapMode: Text.WordWrap
+ elide: Text.ElideNone
+ clip: true
+
+ text: qsTr("You are now in fullscreen mode. Press ESC to quit!")
+ }
+}
diff --git a/examples/webenginequick/nanobrowser/browser.qml b/examples/webenginequick/nanobrowser/browser.qml
deleted file mode 100644
index d63bcd546..000000000
--- a/examples/webenginequick/nanobrowser/browser.qml
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Window
-import QtWebEngine
-
-Window {
- width: 1024
- height: 768
- visible: true
- WebEngineView {
- anchors.fill: parent
- url: "https://www.qt.io"
- }
-}
diff --git a/examples/webenginequick/nanobrowser/icons/3rdparty/COPYING b/examples/webenginequick/nanobrowser/icons/3rdparty/COPYING
new file mode 100644
index 000000000..220881da6
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/icons/3rdparty/COPYING
@@ -0,0 +1 @@
+The icons in this repository are herefore released into the Public Domain.
diff --git a/examples/webenginequick/nanobrowser/icons/3rdparty/go-next.png b/examples/webenginequick/nanobrowser/icons/3rdparty/go-next.png
new file mode 100644
index 000000000..6f3f65d33
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/icons/3rdparty/go-next.png
Binary files differ
diff --git a/examples/webenginequick/nanobrowser/icons/3rdparty/go-previous.png b/examples/webenginequick/nanobrowser/icons/3rdparty/go-previous.png
new file mode 100644
index 000000000..93be3d1ee
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/icons/3rdparty/go-previous.png
Binary files differ
diff --git a/examples/webenginequick/nanobrowser/icons/3rdparty/process-stop.png b/examples/webenginequick/nanobrowser/icons/3rdparty/process-stop.png
new file mode 100644
index 000000000..b68290bf1
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/icons/3rdparty/process-stop.png
Binary files differ
diff --git a/examples/webenginequick/nanobrowser/icons/3rdparty/view-refresh.png b/examples/webenginequick/nanobrowser/icons/3rdparty/view-refresh.png
new file mode 100644
index 000000000..cab4d02c7
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/icons/3rdparty/view-refresh.png
Binary files differ
diff --git a/examples/webenginequick/nanobrowser/nanobrowser.pyproject b/examples/webenginequick/nanobrowser/nanobrowser.pyproject
index dd9039229..c86c57f67 100644
--- a/examples/webenginequick/nanobrowser/nanobrowser.pyproject
+++ b/examples/webenginequick/nanobrowser/nanobrowser.pyproject
@@ -1,3 +1,6 @@
{
- "files": ["quicknanobrowser.py", "browser.qml"]
+ "files": ["quicknanobrowser.py", "ApplicationRoot.qml",
+ "BrowserDialog.qml", "BrowserWindow.qml", "DownloadView.qml",
+ "FindBar.qml", "FullScreenNotification.qml",
+ "resources.qrc"]
}
diff --git a/examples/webenginequick/nanobrowser/quicknanobrowser.py b/examples/webenginequick/nanobrowser/quicknanobrowser.py
index 1191d61fd..aee79c2aa 100644
--- a/examples/webenginequick/nanobrowser/quicknanobrowser.py
+++ b/examples/webenginequick/nanobrowser/quicknanobrowser.py
@@ -4,21 +4,67 @@
"""PySide6 WebEngine QtQuick 2 Example"""
import os
-from PySide6.QtCore import QUrl
-from PySide6.QtQml import QQmlApplicationEngine
-from PySide6.QtWidgets import QApplication
+import sys
+from argparse import ArgumentParser, RawTextHelpFormatter
+from pathlib import Path
+
+from PySide6.QtCore import (QCoreApplication, QFileInfo, QMetaObject, QObject,
+ QUrl, Slot, Q_ARG)
+from PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingleton
+from PySide6.QtGui import QGuiApplication
from PySide6.QtWebEngineQuick import QtWebEngineQuick
+import rc_resources # noqa: F401
+
+
+# To be used on the @QmlElement decorator
+# (QML_IMPORT_MINOR_VERSION is optional)
+QML_IMPORT_NAME = "BrowserUtils"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+def url_from_user_input(user_input):
+ file_info = QFileInfo(user_input)
+ if file_info.exists():
+ return QUrl.fromLocalFile(file_info.absoluteFilePath())
+ return QUrl.fromUserInput(user_input)
+
+
+@QmlElement
+@QmlSingleton
+class Utils(QObject):
+
+ @Slot(str, result=QUrl)
+ def fromUserInput(self, user_input):
+ return url_from_user_input(user_input)
+
+
+if __name__ == '__main__':
+ QCoreApplication.setApplicationName("Quick Nano Browser")
+ QCoreApplication.setOrganizationName("QtProject")
-def main():
- app = QApplication([])
QtWebEngineQuick.initialize()
+
+ argument_parser = ArgumentParser(description="Quick Nano Browser",
+ formatter_class=RawTextHelpFormatter)
+ argument_parser.add_argument("--single-process", "-s", action="store_true",
+ help="Run in single process mode (trouble shooting)")
+ argument_parser.add_argument("url", help="The URL to open",
+ nargs='?', type=str)
+ options = argument_parser.parse_args()
+
+ url = url_from_user_input(options.url) if options.url else QUrl("https://www.qt.io")
+
+ app_args = sys.argv
+ if options.single_process:
+ app_args.extend(["--webEngineArgs", "--single-process"])
+ app = QGuiApplication(app_args)
engine = QQmlApplicationEngine()
- qml_file_path = os.path.join(os.path.dirname(__file__), 'browser.qml')
- qml_url = QUrl.fromLocalFile(os.path.abspath(qml_file_path))
- engine.load(qml_url)
- app.exec()
+ qml_file = os.fspath(Path(__file__).resolve().parent / 'ApplicationRoot.qml')
+ engine.load(QUrl.fromLocalFile(qml_file))
+ if not engine.rootObjects():
+ sys.exit(-1)
+ QMetaObject.invokeMethod(engine.rootObjects()[0], "load", Q_ARG("QVariant", url))
-if __name__ == '__main__':
- main()
+ app.exec()
diff --git a/examples/webenginequick/nanobrowser/rc_resources.py b/examples/webenginequick/nanobrowser/rc_resources.py
new file mode 100644
index 000000000..990f10274
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/rc_resources.py
@@ -0,0 +1,348 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.4.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x03\xa2\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4l;\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x00\x19tEXtSoftware\
+\x00www.inkscape.or\
+g\x9b\xee<\x1a\x00\x00\x034IDAT8\x8d\xb5\
+\x95[h\x1dU\x14\x86\xbf5\xb3\xe7\xe4b\xedE)\
+)(\xd6'o\xed\x83\x85\x0a5\x89\xd0\x82V\x04A\
+E\xad\xfa\x22R\xa5\x14|iII\x0b\x82\xa2`Q\
+\xa4>\xf4EPKi\xccC\x88Z\x0a)bK4\
+\x86&\xa6*\x9a\x1654\xc6K\xd4\xa4\xb1\xc6\x04\xd3\
+\x93\xe4\xcce\xcf^>\x9c9u\x926\xd1\x87t\xc1\
+\xcf\xcc\x865\xdf\xda\xff\xda\x8b\xd9\xa2\xaa\x5c\x8d\xf0\xae\
+\x0a\xf5\xff\x82\x1b\x9b\x83\xae\xfa&\xf3\xe0\x92\x83\xd5\xb1\
+ym\xdd-\xef76\x17v/)\x18`\xc7\xc3/\
+T\xaf\xbby\xe3\xab\xf7\xec5oo\xdb&\xfe\x92\x81\
+}?\xe0\x91\xcd\xdbk6\xad\xbb\xff\xe9\xf3kM\xe7\
+\x96\xe7e\xd9b\xf9&\xbfhh\x0e\xfe\xc0QwY\
+u#\xa1s\xb6\xfa\xcc\xf9N\x1a7l\xadZ\xb1l\
+U\xc3\xc7}m\xfd\x9b\xf6\xca\x96\xd3\xaf\xeb\xc8\x95\xc0\
+\x92\x1f\xb7\x86=\x81\xee\xdf\xd92'A\xd5\xa1(N\
+\x1d]C\xad\x08\x1ew\xaci`l|\xd4\x1d\xebj\
+\xf9\xdbZ{\xef\xe7\x07\xe2\xfe\xff\x04\xbf\xb2\xe3]N\
+\xfd\xd4\x9eA\x15\xc5\x91\xba\x14\xa7\x0e\xa7\x96\xd4YR\
+\xb5\xdcVw7\x1a{\x1c\xfd\xe4\xf0L),>u\
+\xea\x0d\xdb1\xc7\xe5\xfcJ\xa9\xb3D\xb6DdK\xc4\
+iHdC\x924\x22ICb\x1b\x12\xa7!\xb1-\
+\xf1\xcd\xef'\xb8\x98\x8e\xf1\xe4\x03;\xafYq\xed\xea\
+\xb6\x86=A\xd3\xa2`\xeb\x12JI\x91\xc9\xd91.\
+\x14\x87\x19\x9f\xfe\x8d\xa9\xd2\x9f\x84\xb6HBH*\x11\
+Nb\x9c\xc4\x0c\x5c\xe8ah\xe2\x0b\x1e\xbb\xef\x99\xda\
+\x1b\xebnz\xb9\xb1\xd9\x1c\xacp\xcc|p1\x9cd\
+x\xe2[\x94r\x8b\xc4\x03?\xf0\xf0\x9d\xe0\x07>\x05\
+S\xc03>\xe2\x04')j\xa2\xf2S\x10DnX\
+\x10\x9c\xb8h\x0e4\xa8\xf20\x85\xb2<#x\xbe\x05\
+,\xce)\xb7\xae\xacgM\xe1v><qdvj\
+z\xe2Pom\xbakAp>\xfc\xc0\xc3\x0f<L\
+\x95w\xa9\x80g\x04O<\xd6\xaf\xda\x8a?\xbb\x9c\xb6\
+\x8e\xc3\xb3\xd3S3\xfbN\x1fL\xdf\x01\xe0\xa5y`\
+\x11\x09\xea\x9b\xca\xcb\xc77\xecC\x00\xcfH\x19n\x04\
+\xdf\x08'\xc7\xde\xa4\xb6\xa6\x86;\xaf\x7f\x88\xf1\x91)\
+=v\xbcef\xf2\x97\xe8\xd9\xef\xda]7P\x0b\x18\
+\x11\x89T51\x19\xd4\x00\x05\x85\xf1\xb7\xda\xf6\xaf\xbe\
+l\xe7F\xe2\xdd\xcf\xbdX\xa8\xf6\x97\xb3\xf1\xbaG9\
+\xf7\xfd\xb9\xb4\xf3\xb3\x93\x13\xc3\xbd\xba}\xb4\xcf\x0d\x01\
+5\xfc;\x08*\x22ie\xc7>\xe0\xf7\x1d\xb0\xeb\x81\
+\xaaL\xd5\x95\xf7\xfa&\xd3\xebR\xe5\xae\x95O\xd0\xd3\
+\xdd\x13\x7f}\xf6\xcb\x9f\x07>\xb0\xbb.\x8e\xf0\x17P\
+\x00\x1c\x90f\xb2\x80Y\xb4\xc7\xf9\x08K!\x1f}\xda\
+\x11\x0f\x0e\x0e\xf6\x9c9\xe2^K#f\x16Iw\x15\
+p\xbe\x9a\x9f)\x06\x04\xca#\xd2~\xbc5\xfe\xf5\x87\
+\xd1Cg\xdfs\xadYn\x92)\xce\xbe\xcb\xab\xdc\x0a\
+U\xb5\x22B\x06\x22g\xcd\x02&M\xf4\xab\x1f\xfbG\
+Z\x06\x8e\xba\xee\xac\x90\xcbA*\xf0\x04(\x01\xa1\xaa\
+\xea\x9c\x7f\x85\x94\xe9A\xd6\xdb\x80\xf2\xd4T\x1cH\xce\
+\x81\xces\x19\x01\x91\xaa\xdaK\xac\x85.S\x11\xf1)\
+\x9f\xb4\x9fS\xc5M\xfe\xb0R\xbd\x02\xe4\x1f-'\x87\
+\xbf\xf3\xe1\xd64\x00\x00\x00\x00IEND\xaeB`\
+\x82\
+\x00\x00\x05T\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4l;\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x00\x19tEXtSoftware\
+\x00www.inkscape.or\
+g\x9b\xee<\x1a\x00\x00\x04\xe6IDAT8\x8d\xb5\
+\x95iL\x14g\x18\xc7\xff\xef;3;\xbb\xb3K9\
+\x96[\xe4\x10k\x91\xa0\x16E\xa8h\x1b\x8f\x9a&\xad\
+1i\xda\x10\x82\x9a\x98\xa6\x02\xd6O\xb6M\x13\x93&\
+\xfd\xd2415i\xd2Z\x14$5\xb6\x80\xa44i\
+\xb5\xb1Gb<J\xad\x0a\x04A[\x05B=\x10\x16\
+\x01q\x81\xbd\xe7x\x9f~`!\x1c~\xf0K'\xf9\
+g\x92\x99g~\xf3\x9bg\x9e\x99\x97\x11\x11\xfe\x8f\x8d\
+?KQYY\x8b\xf4,u\x1b\xf67g\x97\xbe\xdb\
+\x92\x00\x00\xeci\xc6%\x95\xdf\xaeP\x14\xf5#0l\
+5M+M\x10\x14\x893?\xe7l\xd4\x12tA\x90\
+h\x11\x14\xf8\xa3\xa3\xb6\xd2\x98\xb9\xe6\xa5}\xa7S\x04\
+\xa7\xbfL\x0b[;\xeb+\x1e\xcc\x033\x06V\xfa^\
+s=\xc0\xca_~q\xa9meN\xb2\xecr\xaa\xe0\
+\x9cA\xd7\x0d\x04B\x06\x1e<\x9a\xb4n\xf4z\x02#\
+\xe3\x01\x891\x1c\xf6s\xe5\x0b\x97\xa4s\xe8\xbc\xdd\x12\
+b\x051\x91\xd5Q\xbbg`\x1ex\xe3\x81\xe6\x93\xd9\
+iqe{^_\xadE\x0c\x0b\x13\xfe0b4\x15\
+\x9a\xaa@\xe2\x00\xe3\x0c\xa0i\x03\x7f0\x82K\x9d\xf7\
+\xc3\xd7\xff\x1e\xd2I\xc0\xb3\xa90k\xd9\xad\xfe\x11c\
+r\x22\x98w\xed\xc4\xaeAy\x06ZT\xf5]I\x82\
+K{{{\xf12\xed\xeb\x96v1\xe6\x0dr\xce\x01\
+a\x11\x11\x83\x99\xeav\x85J\x0a2\x9c\x85/\xa4J\
+D@\xc8 l]\x9fk/\xca\xcf\xb0\xf7?|\x12\
+\xf3jQ\x16\xeb\xea\x1d6u!\x89y/\xcf.+\
+\xd5\xcf\xb9Tg\xfd\x99N\x9a\x0a\x18\xccaW\xc2\x96\
+\x10\xdf_\xab\xad\xe0!\x8e\x14\xcf\x88\x7f\xfb\xb9\xd6\xbe\
+\x1f>\xfd\xa65\xd8\xd63LNU\xc2\xd0x\x08O\
+\x02&\xd6\xe7\xa73E\xe6\xb0,\x01\xd9\x1e\x9e\x0f\x06\
+\xc3k\x03\xc3\x93\xcc\x1d\x17\xe3]\x9e\x9dzC\xd7M\
+/ >\x07\x80\x9b5\x15\xde\xf6\xfa\x8a\xb6+5\xe5\
+\xe5\x86nm\xfc\xfdJ_\xfb\x89\x1f;\x82\xf1\x0e\x8e\
+\xcc$\x0d\x97n\x8dB\x918,!Xd\xa11\x09\
+\xa8\x9aC\x1d\xca\xccH\xae\x8d\xd5\xec\x0d`\xbc\xcf\x22\
+\x9bw\xe1\xc4\xb4\xd5Ut\xe9\x86yh*\xa0K1\
+\x0e\x05C\xe3!\x08\x00v\x85\xc3\xb2\x88)B\x16\x00\
+0\xdbcK\x88`\xa2\xcb\xf1e\x8c\xc3\xfe\xb3\xae\xab\
+\x8f)\xc2N\xc5,M\x9eZ\x08.\xaaj\x5c\xa5\xc8\
+\xd2\xd9\xea\xb7\x8a\xd4\xa4x\x0di\x89Nl.H\x86\
+\xaaH0\x051\x9b\xb5\x00\xecHK\xcf9\x8b\xcb\xe2\
+\xcc\xde7\x04\x00\xa0r\xc5\xa2\xf9.\xaajHc\xc4\
+\x7f\xb3,\xd2\x8e\xb6\xb4GH\x10\x88\x00\xc2\xf4^\x08\
+\xb2\xe9V@\x00s>\x90\xe2\xaa\xa61\x06\xb8\x16\xd1\
+\x00\x10\xc1\xc7\xb8\xd8\xfe\xc4\xe6\xeeq\xf9F\x1cO\xab\
+\x01\x00\x85\xa0\xb6\x9f\xda;J\x04\x92\xe7\x1c\x8fk\xf8\
+d\x87l\x939\x00\x06\xc6\x80\xb0n\xe2\xe3\xba\xd6\xc8\
+\xfd\x91\xc9\xf7\xaf\x1f\xdf\xdd\x1d\xad\x8bL\xdb\xd7)@\
+\xbcVZP\x18v\xc7\xe8\xf1\xdd\xff>,\x1d\x1c}\
+\x5cG\x84\xc4y\xad\x00\x11\xb3\xdbd\x5c\xb85\x02\x06\
+Bi^\x12\x9a\xce\xdf\x89\x0c\x8c\xfa\x8e\xb6\x1d\xdf\xd5\
+\xb0\xd0N\x16\xce\x14I\x15\xc7T\x87\xef\xa7\xc9\xb0H\
+\x9a\xf0\xfb\xabl\xb2l\xcd\x9c\x9f3n\xd3\x96\x86)\
+\x90\x91\xe8\x04\x03\xd0\xdd?F\x9c\xb1U\xc5\xef4/\
+]\xf4\xdc\x12\x9e\xb3,\xb1a\xd2\x17\xdc}\xfb\xde\xe0\
+\x87\xbe@8\x9bq\xdc\x9d\xbd\xf1\xac0\x881\x00k\
+r\xe208\x1e\xc4\x90\x97\xe3\xc8\x81-\xf6\xa6\xf3=\
+\xdb\xce]\xe9\xef)\xa9n\xfaJX\xd4\x01b\x03\x5c\
+\xb1d\x22\xe9\x88CU\xe2z\xee\x0em\xb6)\x12\xb9\
+c5\xc3;\x11\xaa_\x04\x9e\x96f\x08\x86t\xe4/\
+\x89E\xc4\xb4\xd0y\xcf\x8bW\x0a3\xa5\xe2\xfct\xad\
+\xe3\xce\xf0\x07\x9e\xc7\xbe\xb0g\xcc\xc7\xc6'\x82N\xc9\
+\xc6\x90\xe2\xd6\x90\x97\x93\x8c\xc48'k\xfc\xa5+\xac\
+\x99z\xe3\x220g\x8c\xdd\x7f4\x85C5\x97&8\
+\x03\xdf\xb1i\xb9m\xcb\xba,\xbb7`\xc2\x22\xa00\
+/M.\xb0R]\x8cM\xf7\xcf\x10\x04\xd3\x14\x98\xf0\
+G\xd0\xf8\xeb\x0d\xbfe\x18\x07/\x9e\xdc\x1b\x9e\x95$\
+\x220\xc6\xa4\xf5\x95\x0dF\xac\xd3\x16\xe8\xef\xba\xb8\xc3\
+\xd3q\xe6Q\xee\x96\xca\x9d\xee%\xb9\xd5\xab\x9fO\xcb\
+\xc8\xcfMQ2Sc\x99\xd3aC\xc4\x100L\x0b\
+\x93\x01\x1d]\xbd\x1eq\xf5\xe6C=81r\xb8\xfb\
+\xf4\xc1\x1a\x00a\x00a\x22\xd2g\xc0\xf6u\xfb\x1a\xbc\
+~\xcf\xed\xb2\xdes\x9f\xfd\x03\xc0\x11\x8d=}\xed\xce\
+5)+\xb7\x95+Z\xfcZp\xa6:U\xc5\x08F\
+L\x89\x08\x10\x11\xdf\x9f\xa3}\xad\xc7\x06\xae6u\x03\
+\x08E\x13$\xa2\xe0\x0c\x98\xad\xde}\xec\xcd\x9b\x0d\xfb\
+/\x03\xb0\x03P\xe7D\x89F\x92\x9cn%!\xa3 \
+!\xe8\x1d\xf6\x06F\xfb\xbc\x00\xf4\xe8\x5cG\xa2P\x1f\
+\x80\x08\x11\xd1\xa2\xa5\x891&\xcd\x81\xcdD\x06 a\
+\xfa7oEcD\xa3\x030\x89\xc8\x9a\xcb\xf9\x0f)\
+i\x5c\x9ar\xdc\xdf`\x00\x00\x00\x00IEND\xae\
+B`\x82\
+\x00\x00\x04\xf8\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4l;\
+\x00\x00\x00\x06bKGD\x00\x00\x00\x00\x00\x00\xf9C\
+\xbb\x7f\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\
+\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\
+\xd5\x0a\x11\x17+\x00C+G\xfb\x00\x00\x04\x85ID\
+AT8\xcb\x8d\x95[hTG\x18\xc7\x7fs\xcen\
+\xf6\x92l\xe2\x0d\xb5\xb5jH[M\x0d\x16\xdcf\xc5\
+\x18\x0d\x16\x95R\x0a\x81\xbeD\xc4\x06\x94\xb6\xd8B)\
+\xb6T\x8b\xd1\x07\x95\xc4z\x81\xfa\xd0\x87B\xfb\x16\xa1\
+PA\x10\xa9\xad\xb6\x09\xa5(\xda\x82\xa6\x18s\xd1\xb4\
+I\x9b\xdb\xba\xbbI\xdc\xdd\xec\xd9\xeb9s\xa6\x0f\xbb\
+\xae\xc6D\xe9\xc0\xc7<\xcc\xcco\xbe\xf9.\xff\x11<\
+c\x1c\x87%\x80x\xc6\x96H\x0b\xd8s-<\xf5\xd0\
+I\xf8B\xc1\xa7\x02\xe4\x5c\xeb\x0a4\x01\xe7%\xbc\xd3\
+\x02\xea\x7f\x81O\xc2\xe9\xea\x86\x86\x8f\xb665\xb9u\
+\x97\x0b\x94*\x9a\xb2m\xb0,\xacT\x8a\x9f\xdb\xdb3\
+\x83}}\xdfK\xd8\xf3$|\x16\xf8\x04\x9c\xa9\xae\xaf\
+\xff`\xdb\xce\x9d\xeex_\x1f\xa9\xf1q\xb0m\xb0\xed\
+<TJ\x94\x94\xb8\x17-b\xfe\x9a5\x5c9w.\
+3\xd4\xdb\xfb\xdd\xe7\xf0\xee\xe3\x1c\xfd\x09\xe8W\xd5u\
+u\xefo\xdb\xb5\xcb\x13\xef\xeb#\x1d\x0a!D\xe1n\
+\xa5\x1e\xcdJa\x19\x06\x96aPS_\xef\x88\xc6\xe3\
+\xd5\xaf\x85\xc3\xcb;\xe0\x87Y\xe0\x13\xf0\xf5\xea@`\
+\xcf\xf6\xe6fO\xac\xa7\x87t$B\xcc\xe3A*\x85\
+\xcb\xb6g\x84\xc3\x10\x82\xe9\x92\x12\x1c\xb1\x18f2\xc9\
+\x9a\xf5\xeb\x9dS\xb1XM\xed\xc4\xc4\xd2\x0e\xf8\xa9\x08\
+>\x01\xdf\xae\xf6\xfb\x9b\xb7\xef\xde\xed\x89\xde\xbeM:\
+\x12!\xeav#\xaa\xaa\xc8\x98&v6[\x84'\x84\
+ \xe1\xf3\xe1]\xbb\x96x8\x8c#\x16\xc3J\xa5x\
+\xc5\xefwF\x13\x89\xb5\xb5\x13\x13\x0b;\xe0\x8a\x06 \
+\x1c\x8e\xf7\xb677{\xa2]]dB!rJ\x91\
+\xb5,\x16\xec\xd8\xc1\xd2}\xfbH\xf9|L\x0b\x81\xa1\
+i\x18ee\xacjme\xc5\xde\xbd\x98B\x90V\x0a\
+#\x12\xe1\xc1\xe0 [\xb7l\xf1h\x1e\xcf'\x00\x1a\
+\x80\xae\xebhB\x90\xbe\x7f\x1f\x0aO\xf7Z\x16\xa13\
+g\x10B\xb0\xec\xc0\x01R\xe5\xe5\x18\xa5\xa5\xbc\xdc\xda\
+\x8a\xb3\xb4\x94\xbf\x0e\x1d\xc2m\x9a\xb8\x00\xa5\x14F8\
+\x8c\xaei\xa8B.\x1c\xc5\xbaT*\x9fy!@)\
+*l\x9b\xf8\xd4\x14\xc1\xd3\xa7Yq\xf0 /\x1e?\
+\x8e\xaeihJ1p\xf80\x04\x83\xf8r9\x94R\
+(\xa5\xb0\x95\x02)\x8bI\xd6\x1eU\xbc*\x96\xd3\xc3\
+\x92*\xcf\xe5`r\x92\xa9\x0b\x17pz<8\xbd^\
+\x22\x17/b\x8d\x8d\x15\xa1\xb6mc?\x84\xcbG\xbd\
+\xe4x\x1c\x8cm\xe7=\xd7\xf2\xf7%t\x1d\xe9v\xb3\
+\xb8\xb1\x11M\x084\xe0\xb9\xc6F\xe2\xb7naI\x89\
+\xea\xeeF\x9a&\xb6i\xa24\x0dJJ\xf2\xce\xcd\xf2\
+X\xca|3HI\x020<\x1e^:v\x0cgY\
+\x19\xff\x9c:E\xdf\xfe\xfdhBPs\xf4(S\xe3\
+\xe3\xc4\xa5$\xd9\xd3C\xf2\xde=\x8c\xfe~\xe4\xf00\
+\xb6i\xce\x04?\x84*)\xc9HIr\xde<V\xb5\
+\xb5\xa1{\xbd\xfc\xdd\xd6F\xa6\xab\x8b\xe8\xd9\xb3\xf4\x1f\
+9\x82\xd3\xe5bc{;\x19\x97\x8b\x14y\x15r,\
+^\x8c\xf2\xf9f\x86BJ\x8998\x88\x98\x9a\x22\x15\
+\x0cb\x99&l\xdaD\xe2\xe6M\xee_\xbaD\xee\xfa\
+uJGG)\x07\x12\x97/\xd3\xadi\xbc\xd0\xd4\x84\
+\xab\xa2\x02+/H\xe8.\x17b\xe9\xd2\x99-\xbd\xcd\
+\xb6\xad\xb1\xe1\xe1\xbau\x0d\x0d\xce\x5c$Bnb\x02\
+g\x22\xc1\x83p\x1852\x82g`\x00\xbb\x00\xd0\xa6\
+\xa7\x89\xde\xb9C.\x99\xc4\x15\x8d\x92\x1b\x19\xc1\xb3r\
+%U\x81\x00=7n\x98\xa1\xc9\xc9\xdb\x1d\xf0\x8d\x0e\
+\xd0\x01W\xebR)5:6\xb6q\xdd\xe6\xcd\xce\x9c\
+a\x90\x09\x06\xd1\x86\x86\xd0C\xa1\x22\xd4.\x98SJ\
+r\xbd\xbddGF(\xad\xac\xa42\x10\xe0No\xaf\
+\xf9\xe7\xdd\xbb\x7f\xd8\xf0z'XE\xad\xe8\x80\xab\x1b\
+\xd3i9:>^\xef\xaf\xafw\xe6\x92I\xb2\xf1x\
+\x11\xa6\xe6\x98\xcb*+YY[\xcb\xef\x9d\x9d\xd9\xfe\
+\xb1\xb1_mx\xb3\x05\xb2\xb3\xd4\xad\x03\xae\xd5e2\
+\xe6h0\xb8\xc9\x1f\x088s\x99\x0c\xd9t\x1at\x1d\
+\xa5\xeb\xa0i\xa8\x82\x95WU\xb1\xdc\xef\xe7\xda/\xbf\
+d\x86b\xb1\x1fmx\xbb\x05\xac\xa7\xe9\xb1\x13X\xf2\
+1|V\xe3\xf3}\xf8\xd6\x86\x0d%\xce\xf9\xf3\x11e\
+e\xf9\xe2/h\xb2\x92\x12\xe5\xf5\xd2y\xfe\xbc\xd9\x1d\
+\x8f\xff\xf6%\x1c\xb2!\x02\x84\x81\xf4\x93\xe0R`9\
+\xf0<\xb0\xac\x09v\xbc\x0ao\x00B\xe5\xf7\x09\x05B\
+\xe4\xa3\xa0\x04\xa8\x01\xe8o\x87s\xc08\x10\x04\xfe\x05\
+\x06\x01S\xcc\xf1Uy\x01\x1fP\x01,\x04\x16\x00\x8b\
+\x80y\x85\x17\x19@\x1cxP\xb0(0\x0d\xc4(\xc4\
+\x17\xe0?\x98\x97d\xf0\xa2y\x08\xf3\x00\x00\x00\x00I\
+END\xaeB`\x82\
+\x00\x00\x03\xbb\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x16\x00\x00\x00\x16\x08\x06\x00\x00\x00\xc4\xb4l;\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x00\x19tEXtSoftware\
+\x00www.inkscape.or\
+g\x9b\xee<\x1a\x00\x00\x03MIDAT8\x8d\xb5\
+\x95Mh\x5cU\x18\x86\x9f\xef\x9cs\xef\xcd\x9fR\xfb\
+#\x18\x8d4\x05\x8d\x88\xa0\x82\xc6\x98\x99,\xda(\x11\
+\xa9\xc1\xfa\x1f\x8a]H\xe9\xa6+k\x13\xa1.\xa2\x88\
+\x1b\x11\x84\x8a.L7\xadbuQ\x8d\x22B-\xb6\
+\x1b\x15\x22\x9d*(\x88A\x94X\xd3\x88\xa16i\xe8\
+df\xee\xb9\xf7s1w\x921\xa6c\x5c\xf4\xc0\xcb\
+9\x8b\x8f\xe7{\xbf\x97{\xee\x11U\xe5J,sE\
+\xa8\x80\xfb?\xc5\xbd\xfb\x83\x01\x11\x0e\x84m\xbe\xff\xd4\
+\xa8\xfaF\xb5kv\xdc7\x12\xee\xbd~S\xe7\x87F\
+l\xdfZ\x0c\xfd'X^\x12\x93\x1bvo\xde\xdcq\
+\xfb\xab{\x06\x0f\xb4X\xe3\xd2\xb5\x18i\xd8y`X\
+Zs\xe2\xc6\xbbo\xdd\x9a\xbf\xaf\xfb\xd1\xa6\xc0\x85k\
+a6\x06\xe7\x9f\x97v\xc1\x9d\xea\xbf{G\xe7\x9d]\
+\xf7\x06\xdfN\x7fN\xcf\xe6A\x8c\xa3R^p\x8b\xb9\
+\xfd\xc1?\xea\xc50\x1b\xb4\xfa\xf6Z\xf6\xab\x82s#\
+\xe1\x1d\xd64\x9d\x18\xdc\xb6\xf3\x9a\xeb6\xdd`'\xa6\
+>\x05\x94$Mxa\xd7[\xcdF\x0c\x82 \xb2\x9c\
+\xe4\xe8\xd8\xee\xf5\xe0\x1d\xb0:\xb8\xf79\xb7\xbd\xb5\xb9\
+\xed\xe8\xc3[\x9fn3\x9121\xf5\x09F,\xd6\x04\
+|1y\x18#\x0e#\x06k,\x82AD\xc8oy\
+\xbcq\x14\xb9\xe1`\xdf\xba\xab7\xbc\xbc\xa3\x7fW\xcb\
+\xf9\xc5)~>{\x06k\x02\x9c\x09H5%M\x13\
+\xacq\x18\xb1\xf8\xd4`2\xc7I\x1a\xa3(\xb2\x1a8\
+?\xe2\x0e\xb6o\xecxf \xffX\xcb\xe4\xf9o\x98\
+\x9e\xfbi\x19\xa8\x09\x81\xa4\xa4\xb6BE<\x89\xc6\xa0\
+\xe0\x08\x89\xecUx\x8dYy\x83\x97\x1d\x1b\xda1\x88\
+\x92\x10\xfb\x12e_$\x16\x8b\xa2\xd8\x10\xc0cP\x8c\
+\x15\xc4T\xbdi\xaah\x09\x8a\xe9\x85\x7fE\xb1\x94\xfe\
+W\xcd\xc9\x13\xe7\xfe\xfc\xfd\xd0G'\xdf\xbdt\xd3\x86\
+n\xba\xae\xbd\x87\xb2/\x92\x9a\x12\xd8\x0a.\x82\xb0\xd9\
+\x12\xb5X\x9aZ-Qk\xf5\x1c4\x1bR\xa9\x5c\x1e\
+\xac\xa3\x9a~\xfd\x9a\xdf\xf7\xd7\xec\x85\xe1c\xc7\x0f\x17\
+\xd7\x87\x1d\xf4t>D\x10\x04\xd8\xc0\xe0BC\x10\x99\
+*|\x09jq\x91\xc1XA\x81\x85s\xcb\xbc\xa5(\
+D$\x00\xa2\x897\xfc\x07\xb7=U\x9c\x19O\xde;\
+r\x7f\xef\xf6\xb6\xbe\xce'\xa50\xfb1j*<\xb8\
+\xe5Y\x9cs\xd8\xc0\xa0\xaa\xa4^I|uG`\xe6\
+\x0c-\x22\x12\xabj\xec2\xa8\x03\xc2L\xd1\x0f\xef'\
+\xdf\xdd\x98\xe7\x91\xe3~\xfc\x9d\xdc]\xdb6\xe6o\xd9\
+\xe9N\xcf\x1eCSx\xfd\xd0+\xe5r1\x8eV\x8e\
+.07\x7f\x16\x03\x84\x22\x92\xd4\x1c\xdbL\xae\xa6\xdf\
+\xbeL\xfe\x98\xff\x95\xa1\xc4\x9f<8\x7fq\xbe+\xd7\
+3\x14j\x22\xc4e\x0d\x0ac\xfe\x81\xf2E\x16\x80r\
+\xa6R\xb6\xd7X\xae\xe1Oh~\x9a\x85\xc2\xdb\xc9\xde\
+B\xe1\xf4\xc9\xcfN\x8c\x97\x17\x8b\xa5F\xe5\xf5+\xad\
+9N2\xf9:\xf7\x15@|\x09-\x8c\xa5/V\x86\
+~\x99\xbcT<\xb2\x874\x8d\xc4P\xcc\x5c\xc6Y\x9d\
+_\xa1j\x14\xaa\xeaE$\x8b\xaa\xda\xb1\xae\x91\x03\xdc\
+\xf7G\x93\xb1\xca\xe0\xcc\x8f\xeb6\xcb\xee$f.\x1b\
+\xdd\xd7\xc1c`\x11(\xa9\xaaJ\xfd\x8d\x91*=\x00\
+\xa2lwu\x13H&\xcdT?e\x19(\xab.\xbf\
+*r\xb9\xc7TD,\xd5\xef\xdc\xd6\xa96Mm\xa2\
+\x04Ht\x15\xc8\xdfq\xe6X\xba\xbc$\xce\xad\x00\x00\
+\x00\x00IEND\xaeB`\x82\
+"
+
+qt_resource_name = b"\
+\x00\x05\
+\x00o\xa6S\
+\x00i\
+\x00c\x00o\x00n\x00s\
+\x00\x0b\
+\x0c+\x1f\xc7\
+\x00g\
+\x00o\x00-\x00n\x00e\x00x\x00t\x00.\x00p\x00n\x00g\
+\x00\x10\
+\x08\x15\x13g\
+\x00v\
+\x00i\x00e\x00w\x00-\x00r\x00e\x00f\x00r\x00e\x00s\x00h\x00.\x00p\x00n\x00g\
+\x00\x10\
+\x08\xea\xfbg\
+\x00p\
+\x00r\x00o\x00c\x00e\x00s\x00s\x00-\x00s\x00t\x00o\x00p\x00.\x00p\x00n\x00g\
+\x00\x0f\
+\x0e6v\xc7\
+\x00g\
+\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00.\x00p\x00n\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x03\xa6\
+\x00\x00\x01{\xe0\xa8\xe4\xe2\
+\x00\x00\x00R\x00\x00\x00\x00\x00\x01\x00\x00\x08\xfe\
+\x00\x00\x01{\xe0\xa8\xe4\xe2\
+\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01{\xe0\xa8\xe4\xe2\
+\x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xfa\
+\x00\x00\x01{\xe0\xa8\xe4\xe2\
+"
+
+def qInitResources():
+ QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
diff --git a/examples/webenginequick/nanobrowser/resources.qrc b/examples/webenginequick/nanobrowser/resources.qrc
new file mode 100644
index 000000000..829285ec0
--- /dev/null
+++ b/examples/webenginequick/nanobrowser/resources.qrc
@@ -0,0 +1,8 @@
+<RCC>
+ <qresource prefix="/icons">
+ <file alias="go-next.png">icons/3rdparty/go-next.png</file>
+ <file alias="go-previous.png">icons/3rdparty/go-previous.png</file>
+ <file alias="process-stop.png">icons/3rdparty/process-stop.png</file>
+ <file alias="view-refresh.png">icons/3rdparty/view-refresh.png</file>
+ </qresource>
+</RCC>