aboutsummaryrefslogtreecommitdiffstats
path: root/examples/webenginewidgets
diff options
context:
space:
mode:
Diffstat (limited to 'examples/webenginewidgets')
-rw-r--r--examples/webenginewidgets/markdowneditor/document.py24
-rw-r--r--examples/webenginewidgets/markdowneditor/main.py20
-rw-r--r--examples/webenginewidgets/markdowneditor/mainwindow.py137
-rw-r--r--examples/webenginewidgets/markdowneditor/mainwindow.ui107
-rw-r--r--examples/webenginewidgets/markdowneditor/markdowneditor.pyproject8
-rw-r--r--examples/webenginewidgets/markdowneditor/previewpage.py18
-rw-r--r--examples/webenginewidgets/markdowneditor/rc_markdowneditor.py852
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt16
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt19
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css260
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js1514
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json35
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/default.md12
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/index.html32
-rw-r--r--examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc8
-rw-r--r--examples/webenginewidgets/markdowneditor/ui_mainwindow.py115
-rw-r--r--examples/webenginewidgets/notifications/doc/notifications.pngbin0 -> 21374 bytes
-rw-r--r--examples/webenginewidgets/notifications/doc/notifications.rst8
-rw-r--r--examples/webenginewidgets/notifications/main.py57
-rw-r--r--examples/webenginewidgets/notifications/notificationpopup.py68
-rw-r--r--examples/webenginewidgets/notifications/notifications.pyproject3
-rw-r--r--examples/webenginewidgets/notifications/resources/icon.pngbin0 -> 2252 bytes
-rw-r--r--examples/webenginewidgets/notifications/resources/index.html91
-rw-r--r--examples/webenginewidgets/simplebrowser/browser.py69
-rw-r--r--examples/webenginewidgets/simplebrowser/browserwindow.py500
-rw-r--r--examples/webenginewidgets/simplebrowser/certificateerrordialog.ui133
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/COPYING1
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/dialog-error.pngbin0 -> 1645 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.pngbin0 -> 1682 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.pngbin0 -> 1263 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.pngbin0 -> 1219 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.pngbin0 -> 1200 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.pngbin0 -> 1927 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json24
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/text-html.pngbin0 -> 1759 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.pngbin0 -> 2024 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/AppLogoColor.pngbin0 -> 6113 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/ninja.pngbin0 -> 1671 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/data/rc_simplebrowser.py1391
-rw-r--r--examples/webenginewidgets/simplebrowser/data/simplebrowser.qrc16
-rw-r--r--examples/webenginewidgets/simplebrowser/doc/simplebrowser.rst179
-rw-r--r--examples/webenginewidgets/simplebrowser/doc/simplebrowser.webpbin0 -> 67542 bytes
-rw-r--r--examples/webenginewidgets/simplebrowser/downloadmanagerwidget.py51
-rw-r--r--examples/webenginewidgets/simplebrowser/downloadmanagerwidget.ui104
-rw-r--r--examples/webenginewidgets/simplebrowser/downloadwidget.py109
-rw-r--r--examples/webenginewidgets/simplebrowser/downloadwidget.ui78
-rw-r--r--examples/webenginewidgets/simplebrowser/main.py45
-rw-r--r--examples/webenginewidgets/simplebrowser/passworddialog.ui121
-rw-r--r--examples/webenginewidgets/simplebrowser/simplebrowser.pyproject6
-rw-r--r--examples/webenginewidgets/simplebrowser/tabwidget.py241
-rw-r--r--examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py87
-rw-r--r--examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py76
-rw-r--r--examples/webenginewidgets/simplebrowser/ui_downloadwidget.py86
-rw-r--r--examples/webenginewidgets/simplebrowser/ui_passworddialog.py96
-rw-r--r--examples/webenginewidgets/simplebrowser/webpage.py29
-rw-r--r--examples/webenginewidgets/simplebrowser/webpopupwindow.py53
-rw-r--r--examples/webenginewidgets/simplebrowser/webview.py294
-rw-r--r--examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py276
-rw-r--r--examples/webenginewidgets/tabbedbrowser/browsertabwidget.py244
-rw-r--r--examples/webenginewidgets/tabbedbrowser/doc/tabbedbrowser.pngbin37147 -> 0 bytes
-rw-r--r--examples/webenginewidgets/tabbedbrowser/doc/tabbedbrowser.rst58
-rw-r--r--examples/webenginewidgets/tabbedbrowser/downloadwidget.py148
-rw-r--r--examples/webenginewidgets/tabbedbrowser/findtoolbar.py99
-rw-r--r--examples/webenginewidgets/tabbedbrowser/historywindow.py103
-rw-r--r--examples/webenginewidgets/tabbedbrowser/main.py395
-rw-r--r--examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyproject5
-rw-r--r--examples/webenginewidgets/tabbedbrowser/webengineview.py92
-rw-r--r--examples/webenginewidgets/widgetsnanobrowser/doc/widgetsnanobrowser.png (renamed from examples/webenginewidgets/simplebrowser/doc/simplebrowser.png)bin51615 -> 51615 bytes
-rw-r--r--examples/webenginewidgets/widgetsnanobrowser/doc/widgetsnanobrowser.rst8
-rw-r--r--examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py (renamed from examples/webenginewidgets/simplebrowser/simplebrowser.py)52
-rw-r--r--examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.pyproject3
71 files changed, 7207 insertions, 1469 deletions
diff --git a/examples/webenginewidgets/markdowneditor/document.py b/examples/webenginewidgets/markdowneditor/document.py
new file mode 100644
index 000000000..331fbc0ca
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/document.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+from PySide6.QtCore import QObject, Property, Signal
+
+
+class Document(QObject):
+
+ textChanged = Signal(str)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._text = ''
+
+ def text(self):
+ return self._text
+
+ def setText(self, t):
+ if t != self._text:
+ self._text = t
+ self.textChanged.emit(t)
+
+ text = Property(str, text, setText, notify=textChanged)
diff --git a/examples/webenginewidgets/markdowneditor/main.py b/examples/webenginewidgets/markdowneditor/main.py
new file mode 100644
index 000000000..4d787f0f0
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/main.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 Markdown Editor Example"""
+
+import sys
+
+from PySide6.QtCore import QCoreApplication
+from PySide6.QtWidgets import QApplication
+
+from mainwindow import MainWindow
+import rc_markdowneditor # noqa: F401
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ QCoreApplication.setOrganizationName("QtExamples")
+ window = MainWindow()
+ window.show()
+ sys.exit(app.exec())
diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.py b/examples/webenginewidgets/markdowneditor/mainwindow.py
new file mode 100644
index 000000000..6f74cf93d
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/mainwindow.py
@@ -0,0 +1,137 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+from PySide6.QtCore import QDir, QFile, QIODevice, QUrl, Qt, Slot
+from PySide6.QtGui import QFontDatabase
+from PySide6.QtWebChannel import QWebChannel
+from PySide6.QtWidgets import QDialog, QFileDialog, QMainWindow, QMessageBox
+
+from ui_mainwindow import Ui_MainWindow
+from document import Document
+from previewpage import PreviewPage
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_file_path = ''
+ self.m_content = Document()
+ self._ui = Ui_MainWindow()
+ self._ui.setupUi(self)
+ font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
+ self._ui.editor.setFont(font)
+ self._ui.preview.setContextMenuPolicy(Qt.NoContextMenu)
+ self._page = PreviewPage(self)
+ self._ui.preview.setPage(self._page)
+
+ self._ui.editor.textChanged.connect(self.plainTextEditChanged)
+
+ self._channel = QWebChannel(self)
+ self._channel.registerObject("content", self.m_content)
+ self._page.setWebChannel(self._channel)
+
+ self._ui.preview.setUrl(QUrl("qrc:/index.html"))
+
+ self._ui.actionNew.triggered.connect(self.onFileNew)
+ self._ui.actionOpen.triggered.connect(self.onFileOpen)
+ self._ui.actionSave.triggered.connect(self.onFileSave)
+ self._ui.actionSaveAs.triggered.connect(self.onFileSaveAs)
+ self._ui.actionExit.triggered.connect(self.close)
+
+ self._ui.editor.document().modificationChanged.connect(self._ui.actionSave.setEnabled)
+
+ defaultTextFile = QFile(":/default.md")
+ defaultTextFile.open(QIODevice.ReadOnly)
+ data = defaultTextFile.readAll()
+ self._ui.editor.setPlainText(data.data().decode('utf8'))
+
+ @Slot()
+ def plainTextEditChanged(self):
+ self.m_content.setText(self._ui.editor.toPlainText())
+
+ @Slot(str)
+ def openFile(self, path):
+ f = QFile(path)
+ name = QDir.toNativeSeparators(path)
+ if not f.open(QIODevice.ReadOnly):
+ error = f.errorString()
+ QMessageBox.warning(self, self.windowTitle(),
+ f"Could not open file {name}: {error}")
+ return
+ self.m_file_path = path
+ data = f.readAll()
+ self._ui.editor.setPlainText(data.data().decode('utf8'))
+ self.statusBar().showMessage(f"Opened {name}")
+
+ def isModified(self):
+ return self._ui.editor.document().isModified()
+
+ @Slot()
+ def onFileNew(self):
+ if self.isModified():
+ m = "You have unsaved changes. Do you want to create a new document anyway?"
+ button = QMessageBox.question(self, self.windowTitle(), m)
+ if button != QMessageBox.Yes:
+ return
+
+ self.m_file_path = ''
+ self._ui.editor.setPlainText("## New document")
+ self._ui.editor.document().setModified(False)
+
+ @Slot()
+ def onFileOpen(self):
+ if self.isModified():
+ m = "You have unsaved changes. Do you want to open a new document anyway?"
+ button = QMessageBox.question(self, self.windowTitle(), m)
+ if button != QMessageBox.Yes:
+ return
+ dialog = QFileDialog(self)
+ dialog.setWindowTitle("Open MarkDown File")
+ dialog.setMimeTypeFilters(["text/markdown"])
+ dialog.setAcceptMode(QFileDialog.AcceptOpen)
+ if dialog.exec() == QDialog.Accepted:
+ self.openFile(dialog.selectedFiles()[0])
+
+ @Slot()
+ def onFileSave(self):
+ if not self.m_file_path:
+ self.onFileSaveAs()
+ if not self.m_file_path:
+ return
+
+ f = QFile(self.m_file_path)
+ name = QDir.toNativeSeparators(self.m_file_path)
+ if not f.open(QIODevice.WriteOnly | QIODevice.Text):
+ error = f.errorString()
+ QMessageBox.warning(self, self.windowTitle(),
+ f"Could not write to file {name}: {error}")
+ return
+ text = self._ui.editor.toPlainText()
+ f.write(bytes(text, encoding='utf8'))
+ f.close()
+ self._ui.editor.document().setModified(False)
+ self.statusBar().showMessage(f"Wrote {name}")
+
+ @Slot()
+ def onFileSaveAs(self):
+ dialog = QFileDialog(self)
+ dialog.setWindowTitle("Save MarkDown File")
+ dialog.setMimeTypeFilters(["text/markdown"])
+ dialog.setAcceptMode(QFileDialog.AcceptSave)
+ dialog.setDefaultSuffix("md")
+ if dialog.exec() != QDialog.Accepted:
+ return
+ path = dialog.selectedFiles()[0]
+ self.m_file_path = path
+ self.onFileSave()
+
+ def closeEvent(self, event):
+ if self.isModified():
+ m = "You have unsaved changes. Do you want to exit anyway?"
+ button = QMessageBox.question(self, self.windowTitle(), m)
+ if button != QMessageBox.Yes:
+ event.ignore()
+ else:
+ event.accept()
diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.ui b/examples/webenginewidgets/markdowneditor/mainwindow.ui
new file mode 100644
index 000000000..f4e29ad95
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/mainwindow.ui
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MarkDown Editor</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QPlainTextEdit" name="editor"/>
+ <widget class="QWebEngineView" name="preview" native="true"/>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>26</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menu_File">
+ <property name="title">
+ <string>&amp;File</string>
+ </property>
+ <addaction name="actionNew"/>
+ <addaction name="actionOpen"/>
+ <addaction name="actionSave"/>
+ <addaction name="actionSaveAs"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <addaction name="menu_File"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="actionOpen">
+ <property name="text">
+ <string>&amp;Open...</string>
+ </property>
+ <property name="toolTip">
+ <string>Open document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="actionSave">
+ <property name="text">
+ <string>&amp;Save</string>
+ </property>
+ <property name="toolTip">
+ <string>Save current document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+S</string>
+ </property>
+ </action>
+ <action name="actionExit">
+ <property name="text">
+ <string>E&amp;xit</string>
+ </property>
+ <property name="toolTip">
+ <string>Exit editor</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="actionSaveAs">
+ <property name="text">
+ <string>Save &amp;As...</string>
+ </property>
+ <property name="toolTip">
+ <string>Save document under different name</string>
+ </property>
+ </action>
+ <action name="actionNew">
+ <property name="text">
+ <string>&amp;New</string>
+ </property>
+ <property name="toolTip">
+ <string>Create new document</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+N</string>
+ </property>
+ </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/examples/webenginewidgets/markdowneditor/markdowneditor.pyproject b/examples/webenginewidgets/markdowneditor/markdowneditor.pyproject
new file mode 100644
index 000000000..e18e05096
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/markdowneditor.pyproject
@@ -0,0 +1,8 @@
+{
+ "files": ["document.py",
+ "main.py",
+ "mainwindow.py",
+ "mainwindow.ui",
+ "previewpage.py",
+ "resources/markdowneditor.qrc"]
+}
diff --git a/examples/webenginewidgets/markdowneditor/previewpage.py b/examples/webenginewidgets/markdowneditor/previewpage.py
new file mode 100644
index 000000000..35ac80be4
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/previewpage.py
@@ -0,0 +1,18 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtGui import QDesktopServices
+from PySide6.QtWebEngineCore import QWebEnginePage
+
+
+class PreviewPage(QWebEnginePage):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ def acceptNavigationRequest(self, url, type, isMainFrame):
+ # Only allow qrc:/index.html.
+ if url.scheme() == "qrc":
+ return True
+ QDesktopServices.openUrl(url)
+ return False
diff --git a/examples/webenginewidgets/markdowneditor/rc_markdowneditor.py b/examples/webenginewidgets/markdowneditor/rc_markdowneditor.py
new file mode 100644
index 000000000..aa4f38a45
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/rc_markdowneditor.py
@@ -0,0 +1,852 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.2.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x01\xdd\
+#\
+# WebEngine Mark\
+down Editor Exam\
+ple\x0a\x0aThis exampl\
+e uses [QWebEngi\
+neView](http://d\
+oc.qt.io/qt-5/qw\
+ebengineview.htm\
+l)\x0ato preview te\
+xt written using\
+ the [Markdown](\
+https://en.wikip\
+edia.org/wiki/Ma\
+rkdown)\x0asyntax.\x0a\
+\x0a### Acknowledgm\
+ents\x0a\x0aThe conver\
+sion from Markdo\
+wn to HTML is do\
+ne with the help\
+ of the\x0a[marked \
+JavaScript libra\
+ry](https://gith\
+ub.com/chjj/mark\
+ed) by _Christop\
+her Jeffrey_.\x0aTh\
+e [style sheet](\
+https://kevinbur\
+ke.bitbucket.io/\
+markdowncss/)\x0awa\
+s created by _Ke\
+vin Burke_.\x0a\
+\x00\x00\x02\xb2\
+<\
+!doctype html>\x0a<\
+html lang=\x22en\x22>\x0a\
+<meta charset=\x22u\
+tf-8\x22>\x0a<head>\x0a \
+<link rel=\x22style\
+sheet\x22 type=\x22tex\
+t/css\x22 href=\x223rd\
+party/markdown.c\
+ss\x22>\x0a <script s\
+rc=\x223rdparty/mar\
+ked.js\x22></script\
+>\x0a <script src=\
+\x22qrc:/qtwebchann\
+el/qwebchannel.j\
+s\x22></script>\x0a</h\
+ead>\x0a<body>\x0a <d\
+iv id=\x22placehold\
+er\x22></div>\x0a <sc\
+ript>\x0a 'use str\
+ict';\x0a\x0a var pla\
+ceholder = docum\
+ent.getElementBy\
+Id('placeholder'\
+);\x0a\x0a var update\
+Text = function(\
+text) {\x0a pl\
+aceholder.innerH\
+TML = marked(tex\
+t);\x0a }\x0a\x0a new Q\
+WebChannel(qt.we\
+bChannelTranspor\
+t,\x0a function(\
+channel) {\x0a \
+ var content = c\
+hannel.objects.c\
+ontent;\x0a up\
+dateText(content\
+.text);\x0a co\
+ntent.textChange\
+d.connect(update\
+Text);\x0a }\x0a )\
+;\x0a </script>\x0a</\
+body>\x0a</html>\x0a\x0a\x0a\
+\x0a\
+\x00\x00\x06V\
+\x00\
+\x00\x17ex\x9c\xb5XKs\xdb6\x10\xbe\xfbW\xec\
+\xd8\x93\xc6\xce\x90\x0e%Q\xb2DM\x0f\x99\x1c\xda\x1c\
+\xda\xe9!\xb7\xc6\x07\x90\x04E\x8c)\x82\x05!\xcbN\
+\xc6\xff\xbd\x8b\x07)\xf0\xa1D\x89\x12\xd9z\x10\x0b\xec\
+.\xbe\xdd\xfd\x16d\xcc\xd3\xe7/\x17\x80\xaf-\x11\x1b\
+VF\x10\x00\xd9I\xbe\xd6c\x19/\xa5\x9f\x91-+\
+\x9e#\xf8\x83r\x9cA<\xf8\x87\x14D\xb2\x92{P\
+S\xc1235\xe1\x05\x17\x11\x5c\x85\xfae\xc6\x0aV\
+R?\xa7l\x93\xcb\x08&kk\xe6\xc9\xdf\xb3T\xe6\
+\x11\xac\x16A\xf5dF+\x92\xa6\xac\xdcD0\xd3C\
+/\x17\xf9\xc4\x83|\x8a\xef\x19\xbeC\xf8\xd2\xb11\xd1\
+/\xc7\xc5\xbd\xb5\x11\x06\xc1p1\xbe\xe7\x1eT\xe0n\
+\xd3\x8f\xb9\x94|\x1b\xc14\x1c\xb8`U\xd8\xf9Z\x7f\
+\xcd>S\xd4\xbe\xb4\xbeM\x87\xb2\xd9\xa2\xd1\xf3\xf6\x0d\
+|\xcc)\x18\x03\xd6\x1c\xb0\x1a\xea-)\x8a[\xf8 \
+_\xd7\x90\xd2\x9amJ\x9a\x82\xe4\x10S\xd8\xd5\xf8s\
+\xcfd\x0e\x1bA\x9eaK%\x01I\x9f\xa4\xd6\x07o\
+pJ\xc1\xf7@\xa0\xe2\xb5\x04\xc9dAo\xe1\xcd\xdb\
+N\xd4\xd4>0t\x0b\xeb\xe1l\xe8\xa1\xd9)\xca\xc2\
+\x11\xd9\xc4\xca\xe6C\xd9\xc4\xee\x9a\xf4\x82\x10\x04\xabU\
+\x96\xad\xbb\xb93\x84R]?R!YB\x0a\x9f\x14\
+\xb8\xed\x08bRS\x95\x1bZm\x94s\x94[\xe5j\
+\xd7~J\x13.0\xc38N-\xb9\x9a\xe6\xda\xcd\xb2\
+\xc5\xc2\x84\x99D\x8f\xacf\x12\xb1\xebxV\xedDU\
+h\xdd\xbb\xc2\x03^Xi\xdf)\xc7\xe7\x97\x8b\x82\xd9\
+Y\x9d\x94m C\xa9R\xa5\xbf\xbaiT\xd0\xcc\x99\
+Wy\xd01\xe9\xa2\xd8\xe6\xc7\x88\x85^]\xccC[\
+\x04\x95\xa0\x03\xe71\xcc\xa3\x8b\x96A[L\xfb\x1cA\
+\xf1\xeb\x8a$h\x18u\xf8{A*\xa5/\xe1)u\
+]k*\xfb=/k^\x90\xda\x83\xbfxI\x12,\
+\xecweJ\x0a\xaa.\xf1b\x8b\x9fZ\xdbXU\xdf\
+\xce\xd7\x83\xbd\xcel\xc6\xd4\xac\xb5\x97\xb2\xba*\x08\xda\
+\x8a\x0b\x9e<\xd8%\x05'\xa8B(M\xd6s\xb3\x97\
+\xd9\xca\x02\xa0'\xff\xb7\xe3\xb2Q\x13s\x91Ra`\
+\xbf\x9d\xd3-\xa0\xdf,\x85+Ji?\xf5`J\xb7\
+\xebA\xac\x82\x01p\xe1\xddb`\x0c\x12&\xe9 \x88\
+\x93p,\x86\xd3\x16w\x93\x7fWq\xa6\xfez\x1a\x95\
+\xc2(\xa6\x19oC\x9a\xa0^Z\xe2\xfe_\x7f\x9a\x06\
+\x93\x10>\x05\xc1\xbb\xe0\xb5Z\xe6\xae\xabzU\xb7X\
+,\x86;X4\xa4\xd9\x94Q7\x91\xda\xc2\xb2\xe5\xa7\
+\x80\xe8\xd5\x80\xe6|\xfc\x0a\xba\xb5\xb6Z\xad\xb4C\xc8\
+i\xefU\xee\x18\x22\x929\xd2\x99\x82@\xd1Z\xc2\xab\
+g\x1dA\xf8\x88\x0c&\xb1\x92?\x94\x89\xa6\xa7\x8bx\
+\x87\x14Xz\x17\xac\xacv\xd2\xbb\xc0\x9a\xa7\x09~+\
+W\x88\xa0\x86O\xdc\xb4\x09\x82W\xca|\x87L\x8eS\
+\x07\x12c_\xb8eij*\xdf\x9a\x06mZ\x1b\xea\
+dm\xc9\x05\xb2\xb1\xd6\xa1\xc8\x07\x13q\x1f\x81\x22\x93\
+\xd8]\x1eE\xfe\x96\x7f\xf63\x9e\xecj\x9f\x95%\x15\
+V\xe1P\xa0M\x98\xe4\xb4\x8ew\xdbI\x07\x8a\x7f\xe5\
+sE\x7f\xbf4c\x97\xf7\xddQAk*\xfb\x83\xf5\
+.\xde2\x1c\xd5f\x92\x9d\xa85\xcfq\x86\x19$\x94\
+1l\x80\xf1\x03\xc3\x00W\x15%\x82\x94\xaa\xf0\x8dz\
+e\xdc\xd1\x94\xe44y\x88\xf9\xd3\xbd\xdd\x89\x19\x15$\
+e\xfc\x88\xf2\x17\x15|\x05\x92P\xc5\x9c\xd2\x8c\xec\x0a\
+\x09I.\xf8\x96\xc2o\x901\x81I\xfd\x84'\x01\x89\
+G\x82M\xad\x02o0*\xb9\xbc\xb6\xee\xb3-\xd9\xd0\
+\xcb\xfb\x1b\x0f:\xb1o\xbcF\x7fT\x0ah\xb4lU\
+\xa81\xbd1\x85\xf3W\xe4GE\x98\xb4\x1d\x00\x11\x96\
+$\xb7\x00\x8e\xa1\xa5\x1c\xcb\x18-\xd2\xf5/wl\xcc\
+/\xcc(k\xd3\x8c8=\xf0\xa8\xcb\xa65bw\x22\
+X\x96'TY\xc3\xf6\x97\x7f\xd2\xe2\x91\xaa\xc2\x81\xbf\
+\xe9\x8e^z\xd0\x0e \xf1\x0bF\xb0\x85\xd5\xa4\xac\xfd\
+\xf6t7\xe0\xf6\xde\xb1\xebPNG\xaa\xacw\xe8j\
+\x8e\x14g%\xe6@k\xd0S\xa9\x10\xe8\xd6QE\xea\
+z\x8fEz\x7f\x80\xa7\x01L\xdbh\x9b\x14+\xf5F\
+\xda^e\xf9t:\xb1|\xda\x16w\xe8\xa0\xf1\xfd\x00\
+\x19\x1c\x00\xfa\xd7\x0d\xfd.\x03\xf5\xb7v\xa8\x05\xcfi\
+M\xcbK\x92\xa4\x9b\xab\xba3*\xc4v5\xb6\xd0\xca\
+M\xd31\xd1\xd8\xe8K\x93>n\x102VP\x13\x83\
+\xf6\xd4rgTt\x8f2wFC'\xf1\x1a\xa1\xb9\
+\xa50\x9dd#\xe83p\xa4e\xc4:\xa19/\xd0\
+\x0d\xcd\x1b\x86U\x9dQ\x13x\x0b\xc6\xa1\xad\x1e\x8aE\
+{y\xc2\x8a\x93\xd3b\xb4z\x1ak\x12\x0b\x0f\xcf\x9b\
+\xfa\x5cj\xd0\xd3\x08\x10\x01\xc1\xed\x14\x0fO\xba\xe6s\
+\x92b\x8bt\xc6\xdb0\xfc\xf8\xf23\x0c\xbbL\xa6g\
+\xa8\xe4F\xb2\xc6^\xafr\x09\xa3\x0eb\x13\x93\xeb\xc0\
+\x03\xfb\x7f;\xb9\xe9\x12\xdcw-\xfb\xde\x15\xc3\x8a\x8d\
+t[\xedd`\x1b\xa0F\xd6\x84\xc7\x5c\xeb aB\
+\xa9\xbd\x1f\xee\x18l~\xdbd\xd0\xb6\x97xO8Y\
+,=\x98\xce\x16\xca\x81\xe5\xcd\x8fc\xe4\xa9\xd3\x12,\
+\x1bY_\xf5\xe2GA<I\xefOWi*\xd3\x1c\
+\x19t-\x9a\x9f_gE\x97\x05a\xe2R\xe1Om\
+5\xa3\x94y\x84\xf6\xc2\xe3\xb4\x17\x8e\xd2^\xd8W7\
+\x0el`\xa0\x9b\xce\xe7\x08[\xfb\x81U\xa6\xc1U3\
+\xa6#\xd0\x07\xf3og\xc1Y\x9a\x7f\x8dR\x92<l\
+\x04\xdf\x95\xa9\x7f\xb8\xd7_\x84I\xda\x13\x0a\x8ag\x11\
+u\xff\xa6\xbf\xfd\xa7\x9eX\x1f\xfa\x22\xf0\x1fr\xb9-\
+\xfc\x8d\x02\x1c\x0fA\xd7\x86\xa0<}\x13\x02\x92W\xf6\
+\x97i\xe2\x1edx\xae\xbc\xbe\x0a\xc2U\x92\xc6\xea\xbc\
+\xc8\xaf\xad\xf1\x9b\xbeo\x8d~\x05\xaeQz0\xa2\xf5\
+Z-^\xe3\xfeQ\x05\xf5y\xebm\xea\x9c\xbaC\x8d\
+\xa9_\xa3\xe0:x\xd5Z\xb9\xe9\x08\xd4\xad\xd0\xc1\xee\
+7\x0c\x9f\xe5<?k\xf9\xe9k\x0fOot\x99\xeb\
+\xdb\xd1&s\x03\xf0\x9d\xa4u\x12r\xdad\xf9\xe0\xf0\
+\x13\x04a\xbc\x22N=\x1bt\x9d\x84\x9de\xcb\x89[\
+\xd9n\x0fER\xac\x9bNI\x8ab\xb4C\x8f\xcc\xf9\
+\x86\xb8\xdbp\xec\xe6\xdd\xef\xc6\xa5\x91\xce\xd4\xe1\xebS\
+\x8644\xed\x8d\xea\xe1\x11Z\x0fg'd\x15o}\
+G\xbc\xe7\x86\xf7\x8e<ok\x15\x93D\xb2G:\xbc\
+[\xebq\x8e\xea;wc\xbdg~\x16?\x9e\xa7\xf6\
+gk<\xfeX@\xe3\xd3}\xbe\xe8<\x0bx\xb9\xf8\
+\x1f\xcb!7\x81\
+\x00\x00&L\
+\x00\
+\x00\x91Dx\x9c\xed=ks\xdbF\x92\xdf\xf5+F\
+r\xd6\x00\xf8\x94\xe4\xacoC=\x98\xac\xb3\xb7\x9b+\
+g\xedJ\xbcUwGP2HB$l\x10\xe0\x02\
+\xa0%\x9d \xff\xf6\xeb\xeey\xe3AIvruU\
+w\xde\x8dM\xce\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4t\x0f\x87\
+\x9d\xce\x1e\xeb\xb0u\x90}\x0c\x17\xac\xcf\x02\xfa\xb8H\
+\xaf\x13\xb6\x09\xb2<\xcc\xb0\xf6U\xba\xb9\xcd\xa2\xe5\xaa\
+`\xee\xdcc\xc7\x87GG}\xf8\xeb\xdb\x1e{\xb5\xca\
+\xa2\xbcH7\xab0c\xff\x16^]e\xe1\xed\x80\xb9\
+?\xff\xf4\x8e\xbd\x8e\xe6a\x92\x87\x0b\x0f\xdb\xaf\x8ab\
+\x93\x8f\x86\xc3eT\xac\xb6\xb3\xc1<]\x0fy\x87\x1f\
+r\xf1\x01\xa0\x86{{'\xee\xd56\x99\x17Q\x9a\xb8\
+Y\x9a\x16\x1e\xbb\xdbs\xb6y\xc8\xf2\x22\x8b\xe6\x85s\
+\xb2\xb77\xe4\xe4\xfe9N\xe7\x1f\xfb\xaf\xc3Oa\xcc\
+\xfe\x9a\x05k@\xc21|\x0a26\xc3Jv\x06\x8d\
+\x19K\xc2\xeb8J\xc2\x11\x1b^\xf8Iw\xd8\x83\xa2\
+y\xba\xa0\xef.\xbb\xfb\xf6~\x02\xc5\xd3\xae\x9ft<\
+^y\x15&\xf30\x1f\xb1$M7\xf8}\x95!(\
+\xbb;\xec\xbd\xb8w\xdd\xf1\xa8\xcf:\xde\xdd\x8b\xde}\
+\x09\x9f/\x8d\xcf~G|\xf1\xf0K\xd2-\xbf\xf1\x08\
+\xdf*\x0c\x16Q\xb2$$\x1d\xf7\xd9\xddQ\xef\xe5\xbd\
+\x07\x9fx\xbfc\xfc8\x1e=\xebB\xe3\xb1\xdd0\xd9\
+\x14\xc1,\x0e5%4\xaa\x7fn\xd3B\x12\x8f$\x9d\
+\xb3\xb1\x0b\xd3\x14,\xb3`\xb3*\x09i\x87S\x00x\
+\xc4\x90b\x98!\xde\x04\xaaf\xdb8\xf6\xd8\xc4\xcf\xfd\
+_\xa1{\x80\x5ce\xe5\x22\xbc*\xfd\xe4\xee\xb8w\xef\
+\x8e\xf7\x194\xdf\xf7\x8f\x10\x8ey\xc0\x97\xd2\xcf;r\
+,\xc5:\x1e1Grc<r\xd8p\xc8\xd2\x0d\xce\
+W\x10\xb3(Y\x84I\x11\xe07\x00f\xac\xcb\x9cS\
+7\x9fg\xd1\xa6(7YX\xe6\xc5m\x1cz\x13\xdf\
+\xcf\xcf\xa7\xf8\xb7\x0f$t\x90\x84\xd3\xa1\xef\x1f\x9d\x03\
+\xf1H\xbd\xcfy@\xa8\xdd#Ob*Af\xd6\x80\
+^@\xb9\x16\xd8\xb1\x06;\xf5\xfd\xb1F\x0e_\xce\x01\
+\xb2\xc3\xc1^\x18`\xfb\x93\x1f\xfa\xffi\x90a\x80}\
+k\x82\xf9\xfe\xe4\xd5\x8f?\xbc\xfb\x01\xfe5\xd1N\xe1\
+\xffF\x93?\x1aM\x86c\xb7\x08\x968\x09\xac[\x02\
+H9\x1c\x9f{\xd6x}\xcelE\xfeK\xa350\
+\xbf\xc62w\x12\xf4\xff\x0bi\xbd\xee\xf3\xd9\x0d\x0aX\
+\x0f\xb3m\x11z\x9d1H\xfd\xf8\xdc\x1d\x9f\xf9\xfe\x0a\
+y\xb7\xbb\xa7\x7f\xf1`\xba\xc2\x84\x01\x81\x06\xc1M}\
+\x9a]\x02\xc6\xceS\xba\x98\xc7i\x0e2o\xf6\xe29\
+(@ hz5\xf9\x137\x0efa\xec\xf9\xd3\x11\
+\xeb\xf8\x09\x8c\xe4t\x8c\xeb\x02\xc4\xa3\xeb\x9d#n\xe2\
+!\xd5\x94\x08\x81\xf2[D\x05P\xe7\x8di\xdd\x18\xcb\
+\xa5\xb2Xbc\xdd\x89\xb5\xe6!\x02\xf7\xac\xec{H\
+o\xb5\xbdZDF\x0b\x82\x00\xe6\xc0\x12\x11\xe8J\x89\
+\xb7\x14\xcb\x0f\xe4m\x88\x94\x1a3\x9e\x94>\xce8N\
+\xe5\xa8\xca\xd6r\xbf\xdf\xf7<AOGP\x1e\xde\xd0\
+\xf2\xe4\xa5\xc3\xbd{\xd0r\xb4\xd6\x07\x97\xc4\x1fPd\
+8C0\x07\xfe\x94\x96\xb6?\x81\xffM\xa7\xb8\xda\xf1\
+_X\xe5'\xb2\x01q\x877\x18\x1d\x10\xec\xc1\x18\xe0\
+\x0e@Z\xbd\xceA\xe9L.\x1cZ=80\xfe\x19\
+\xc8\x00\x06;\xa5\x0fcv=\x98k\xcfS\xe8`\xba\
+\x00W\xb8\x88\x0aW\x15\xa0\xa8\x0e\xb2p\x13\x07\xf3\xd0\
+u\x88@\xa7\xc7Lzm\x08\xa2HC\xf0\xe9C\x88\
+eX\xfc\x12.\xc3\x1b\xd7S\xe3E\x9d\x13\x16\x82\xfc\
+I\xa7\xdb\x9f\x96\xfe\xa2\xeb\x0f4EQ\x11\xae\xb1\xde\
+\xd2e\x17jH\x86\xe6\x12\xaa\xb0Smj\x8c\x06K\
+z\xccY\xae\x1d\x8b\xe4!\x22\x18.%\xc9\x9c\xa86\
+\x92Q\xb7\xdaX\xb1\xe4\x91\xf8\x14\x93V\x19p\xc8A\
+\x95F\xab\xecH\xc8~\xeb^co6\x5c\x8a={\
+\x14\x0e\xcc\x95\x81\xd4\x81E\xa8\xe6p\x90\xa7\xdbl\x1e\
+\xf2u\xd96\xb2K\x10i\x18\x99\x13,\x16Y\x98\xe7\
+e\x90\x15\xd1\x1c$8\xc8\xa3EX\xce\x82\x9c\xffu\
+\x95&E\xa9\xb7\xa6r\x96.n\xcby@{\x82\xb3\
+'T7\xe8\xed0\x03\x0d\x1e\xe3\x7f\xcb,\xddn\xca\
+\xc5\x02\xb6\x9d\x22\x88\xe2\xbc\x5cDA\x9c.\xe1\x1f\xd8\
+\x89\xa2O\xe5\x22.\x17Ey\x15\x85\xf1\x22\x0f\xf1\xc3\
+\xb2\x82\x0eJ\xb6\xb0\xa0\xae\xc0@\x00\xacWi\xb6.\
+\xaf\xc0\x04\x08\xf9\xdf\xd8f59\xea\xbf\x9c\xd2\xa2\xa5\
+\xbf\x00\x0c\xd70l_eD@\x12U\x0c\x83N\x16\
+e\x1c\xc1\xff\x93\x8f\xe5:\x88\x92\x12v\x99-\xfd\x85\
+\xf2\x01\x1f\x8a\xa0L\x82Oe\x92r\xf4%\x8c\x02\xb6\
+<>\x8a\xd4\x22lS\xa2\x16Y\x97yH&L\xc9\
+\xd9\x5c\xe6[4OnKRQeA\x1c*\x16e\
+\x81\x03(\x8b\x15\xfc\x1f\xe9\xa4\x95Q\x16\x99DVd\
+\xc1\xfcc\xb9\x8d\x1d\xb5\xb8\xc5\x0e\x88\xf2\x7f\x0az\x04\
+\x84\xbdO;KN\xaa\xb8\xdf?W\xb2\x8e#\xb5\xa5\
+\x12K@\x1a\xa2\x8a\x90\x08\x94z}\x8a\x82\xca\x1a\x0e\
+\x96\xc6\x0a\x06Mg\xd5\xaa\xcd\x08`\x86\xac\x8b\x1b\x07\
+l\xad\xa3K\xd8=\xae\x07\xa3>-M\xd69c\x9d\
+\x03TD\xb0(\x0fJ\xfa*\x95\x91\xc3\xbf\xa2\xe2?\
+p\xceN\xcf\xdf\x83N\x1a\x0f\xdb\xc4R\xe9i{|\
+\xaa\xb8aa\x09\x0ed\x95*\xae\xc8u=\xff^\xd1\
+oU\xa8\xb8\x11\xac\xc6 \xdc\x09\xd1zf\xf3 a\
+\xb3\x10L#\x10\xd5l\xbb)\xc0\xc4\x9e\xdd\xb2\xe2v\
+\x13\xe2\xb6O\x16\x15o\x97\xb7\xe9D\xb5\xb4\xec\x01\xeb\
+r\x9b\x14\xc5\x08E\x90\xcd\x1a\xab\x0baL\xff\x1d\x96\
+\x10\xd8odS\xdb\xd64\xc7\x90\xf0\xfa3\xb6\x0e\xb3\
+e\xe8\xde\xdd\x0b\xd4\x06\x8a\xbf\xfe\xeb\xcf\xed\xed\x97W\
+\xebzc\x81\xb5GV\xba\xb4\xba\xc9H~Oz\xee\
+3\xe9\xb7\x09\xf3\x07h\xed\xfd\xda\xc5-\x1f4\xbc\x14\
+w\x8f\xcc\x02\xffh\xe7>\xbe\xc3\x02\xef\x1a\x16\xf8\xb3\
+\x8e\x89e\xef^s\x1f(\x7f\xa2\xc4\xc1\xaaD\xb5\x8b\
+\xff\x08\xdbGc\xe2\xa3\x14\xdaW7\x01\x85\xcf5\xf5\
+\xb1\xe3\xd1\xd2\xb7[\xe2\x86\xd2\xde\xe6\x85l\xd3:\xbb\
+85]\xf6\x0e\x95O\xde>G\x05\xaf\xafO\x13\x10\
+\xce\xe7H\x1dH\x88\x8f\x93\x0b\xb0t\xd8t\xd0\xf1\xcb\
+A\x87\x9bV\x93\xfeh\x0a\x07\x19\xbf\x9c\xf4K6\x92\
+\xe7\x10<4\x0d`u\x9fspu6\xe9\xd0\xe1\xc2\
+\xb6\xde\x10\xb3_\xba\x03n\xab\xf9\xe5\x18\xb6y\xc2Z\
+\xc3\xc8va\xe43(\x86\xff6\x5c\x04\x09l]l\
+Y\x1f\xf4F\xd6\xed\x96N~\xee\xa1\xb9\xa7\x99q.\
+H^\xa4>\xee\xf0M\xb8\xf4\xe9\xa4$'\x0fmy\
+T\x05\xd2X\xee\x8e\xf91G\x82\x93\xd1,\x9a\xa0\xba\
+@\xa3\x19\xb4\x83e\x99\xc3\x174\xe4@o\xa2\xd6\xf4\
+Ac\xfa\x0e\x98h\x0e\xb6\xc3\xcf\x07C8\x83\xe4h\
+\xe6\x8c\xc1\xe4l\xc0\xcc\xb7\xf7\xc7j|\xd3h\x81\xae\
+\xd1fAA\xc6\x83\x1e\xd5\x12UA\x09\xbb\x22\x9c\xc7\
+S0\x82s`Q\x5c\xe6\xe5\x1c\xb6\xca\xf2\x9f\xe5\xe2\
+*)\x83\xd9\x0c\xb6\xf1\x006\xcd\x22\x82-\x19\xcf\xda\
+%\x9c\xc9\xcb<Xo\xca\x8f\xb3\x05\xec\x863\x03\x1b\
+|\xdd\x94Q9+a\xcb\x0d\xb2\x8fe\xb6\x9d\xdd\x96\
+YQf\x9br\xb6\x80\xf2EZ\xe6\x9b )\x01\xe9\
+5\xfc\x17%`.\x84\xb0\x8b\xaf\x97\x9e\x81\xc5\xf7g\
+\x1e\x1cU\xc0\xd0\xd9\x1f\xa1Y\xec_\xc3\xf0\xbf\x9fv\
+\xbe\x87\xd2\x99d\x81^\x1b\xe6Q\xa4\x03\xc7\x10h1\
+\xc5\xf3\x01\x9eC\xaaG\x10T\x14\x07\xee\x94+\x8b\xc9\
+\x817\xad\x9c=,Y\xe3\xab\xebux\x13\x0a1\x93\
+\xde\x0c^\xe6rs!\xf7H\xaa\x8aU\x94\x0f\x8a\xf4\
+c\x98\xe0\xb2\x9bLO\xec\xb2\x01Z$Xsw\xaf\
+jD{(\x94\x9f\xcaRxn\xd0\xae\x0b\xb6q\x91\
++\xe0l\xcb\x17\xb4)\xcf@*c\xd1\x15sMt\
+j\x19p\xba\x1a\x9bK\x10\xc4~\xcf\xc28\x0f\xebh\
+@SH\x0c\xb5:\xae^duc\x17\x1c\xe4\x84\x00\
+D\x17;\xa0\xa13\x01\x8a\x14\xed\xdd\xab9\xf8\xcb\xcd\
+\x06\x16\x92\x98\x8a_\xb0\x0d\x9f\x0a\x9a\x01\x1b\x89\x9e\xb8\
+_\xd1m1\xc7Yb?\x87\xc5*]\x98mb(\
+=c\xca1\x95g\xf3\x1e\xb3f\x12}N1\x82\x02\
+X\x12^W&\x1b\xc9\xcc\xc2b\x9b%\x1c\x08\xf1!\
+\x12\xa8\xb87\xd4\x14\xac\xbc,\x85\xed\x01O\xcef\xef\
+PZ\xa4h04\xd0\xc1\xbb\x87\x0fP\x0e\x7fW\x96\
+\xb0\x9f\xa1R\xcah\x19\xfbIU\x13\x0c\xfd\x82jH\
+\xa1\xd5\xea\xb6\x87\x87\xc1!\xafo\xa8<\xfe\xf6\xf8[\
+\x85\x96dJ\x0cP\xcb/gS\x91mC{\x9c0\
+\xa8\xd6\x01R\xc3\x1a\xab\x8btS\x19\xa7&\xe5\x82u\
+\xbf\x19.\xf1\x14\xe7\x10\x9fq&\x128O\xf7\x84\xe0\
+\xc4)\xc8\x82\xfc\x02\xc7\x08\xf9\x11\x0fa\xea\xb3\xfc@\
+\xe7A\xf1\x19\x94\xce\x5c5\x8c\xe4\x07P\x8a\x0a\xb3\xaa\
+\xcc\xd3\x0c\x0e\x18\xe1B\x17\x14A\xfeQ\x7f\x9b\xaf\xc2\
+9,Pb\xd3\xf5*\x82\xf3\xb9\x9e:\x86\x9a_\xb8\
+(\xd5\xc2\x01:a\x9cZ\xe6\x07\x02`\x00\xec\x9aS\
+[\xbd\x8e4O@\xaf\xa2\x974Yb\xfb\xc9\xe1\x14\
+\xc4%Y\x16+\xefD\x12\xc21\xeb\x1av\xce\x8e4\
+\x22[\xf7l\xb6\xf9\xca\xd5U\x8c,\xd6\x11s\x88-\
+\x8e*\xbfW\xd8\xef\xc5Z\x94cB\xbd\xdf> \xac\
+\xfd\xba\xd1p\x94\xa2\xce\x10\x87\xbbo\xefMyx`\
+\x5cbTH\x8e\xd3\xd3\xa5\xe4\x90\xd9oT\x91\x06K\
+\xc6\xd8\xbd\xb1*\x12\x10E\xea\xd8\x80\x19!\x8cd\x91\
+&\x1e\xce\xccQ\xb2\x0dO*L\xe3\xf6!sQ\x91\
+\xb63OX\x91_\xc5\xbe'\xf3$\x0e\xd0\x82Fd\
+\xc7\xd3*\xa7\xb0\xf4\xc5\x14\xb7\x22\xc7y\xf4X\x85U\
+\xde>L\x01\xf0{\x8fS\x1d\xf0T\xc5\x22\xdc\x14+\
+>\xaa#\x89\xb0i\xc8\xc7\xd3G\x0f\x96\xb66\x96\xa4\
+\xb0\x01Pol\x13\xe1\x01\xd0\x9agPs\xec\xf9\xf3\
+\xa6\xd5\xcf\xedn\x83\x11\x9a\x13\xc2\xa9U\x1b\x15\xb50\
+\xc6\xc4\x9d #PkqT\xbc\x0a\xe38w\xc5\xf8\
+\x8c\xb5CNV\xf8\xef\x1bR\xed\x8e\xe7\xe9\xf6A\x1c\
+-\x139l\xbb\x8d\xd9b@\xf8\xdd!\xc734\x10\
+\xcc\xb1O%*c\xf1\xc1\x5c=b\xf1\x08\x14\xb4\xb3\
+\xc0\xf2\x99(&\x9f\xec\x19\x8a\x0c\xc7=\xe0\x83\x92\xda\
+\xec\xec\xec\x8c\xd81 Z\xa5 \x18\xacy\x8c\xd4(\
+\xe0\xab4\x83^\x00\xfe\xf0\x84E\xec\xb4\x8e\x19\x8a\xbb\
+]\x13='\x0cY\xd2\xef\x8e\x90%\x83\x22\xcc\x0bW\
+7\x9cDS\xcfn\xc0\x98U\x8b\xde6\xbadsN\
+\x0c \xc3\xdaB\xe4\xa3\xaf\xc1\xce\x1dq\x0f\xa0\xffb\
+\xecqx\xd5H\xfa\xeef\x09l\xc5V#\xbd\xbb<\
+0\x1d$S;\xa6C\xc1\xf0\x8e\x0c\xd9\xb7\xaaz\xac\
+.M\xdeI\x13\x155m\x82\x0dM\xa9\xb1\xd7\x7f}\
+W\x5ce;\xf4]\xf6\xbb\xab\xba\xec\xf1\xeaY\xbb\x97\
+\xda)\xd60_J\xf9\xa3I\xd7]]\x82u\x95\x15\
+\xe6@\x1e\xb0\x08:\xe7l\xacM\x02\x01\x0c#|\x1b\
+\xe49{\x0fZ\xf7=X\x98\xecc\x18n\x80\x8e\x90\
+\xcd\xb7Y\x06\xabD\xc3\x1d\x00H\x8c\xf7\xcc\x07\x0c\xba\
+\x86\xb1\xb2w@.\xd8u,\xbc\x09\xe6E|\xabA\
+W\xe9\xb5\xba<\x1flbv\x9df\x1f\xf3Am\x90\
+\xc8\x06n\xd6~\x19\x07\xc2d\xd14\xfe\x96\x99D\xf7\
+Q\xfb\x1c\x92s\xe9\xab\xe4\x8e\xae[\xce\xc4\xd6\xa0\x8c\
+Mi\x14\xe3Q\x0b\x00\x0c\x93\xf3\x09cF\xe2\xc4|\
+\xeb\x9dD \x1e\xd5\x0do\xc6\x08\xd6\xa8\x81\x8d\xa6K\
+\xf4\x8dl\xd3DO\xd9_\xc3\x82\x85\xc1|\x85\xd3\xd1\
+\xa7i\xe6\xda\xa0I\xa8\xd6A1_\xb9\x06\xf3\xec\xe5\
+\x8f\xe7\x0e<\xbc\x04\xa0\xf3$\x1f\x04g\xa4\x92\x92\xdc\
+!E&\xdb\x91n\xe3j-\xae\xa91\xb1\xc1#\x05\
+\xd1\xd4\xd04@\xfa/\xe1:\xfd\x14\x92\xd0\xd2-\x14\
+\x82:9\xe3\xf7K&`\x9eB\x1d\x0al\x1e\xc2\xf1\
+*\xc8\xa9\x09QK\xdc\x1fh\xf6\xa1u\xcf\xc46j\
+\x93\xac(\xa1:sy\xb9\xe6E\x1d\xeb\x0e\xed\x85F\
+\x04\xbc\xd9\x16\x18\x19\x00\xa7\x1fX?\x9f\xe0\x98\x0c\x04\
+\x98\xf5\x8a|\x12\xe2 \x02\x81`\x7f\x0b\xe6\x1fo5\
+i(\xbc\x9f\xa9o\x8c2\xb8ys\x85\x16\x02\x1cM\
+m\x95\xcf\x07\xd0o\x19\x81\x1a\xc3\x83\xa6=\x1a\xf7\xd6\
+@\xf1T\xffK\xb8\xfc\xcb\xcd\xc6\xc5\xf0\x87\xa3\x1e\xde\
+\xa7\xf1\xde\xba\xcc\xb9w\xc45b\xd5\xfc\xc7\x03@\x95\
+a\xd0\xb8~F\xb1\xb6\x19\xe0\xc8\x8f!\xec\xd3k8\
+\xf3\x01\xcf\xc2b\xc5Y\xc6\xe7L3k\x16\xc6i\xb2\
+\xcc\xc1\xb8\xcb\xc2\x81\xd9\xfa\xcf\xc0;\x1cU\x8cl\x83\
+\xa9_\xa4p\xaaH\xd2B\xb4`\x11?\xa8\x13*\x9b\
+\xc5\x16cr\xd0e\xc5k\x80\xc9\xd10\x8d\xd8>X\
+X1\xeb\xdb\xa7FP\x00\xca'\xc3E\x8f\xab\x13\x92\
+X`\xce\xd1\xd4\x83\xb5cM\x02\xf4C\x8b\x12\xf1\xcd\
+\x10\xf5\xbe[\xd1\x11X83\x8f\xa9U\xdb\x83\xab'\
+\x5cYy\x1c\x01_\xa9'o\xf0!\x8d\x12a:v\
+\x11\xe6\xc4j\x84\xcb\x8e\xe8\x7f\xd0\xdch\x9c\x01\xe29\
+r\x0d}\x0a\xa0\x86\x90\xa3\x16\xdb\xff\x91\xa3\x0b\xdbE\
+\xc7\xb8G\x8142\xb6\x88_LS\x18\x8d\x09\x8f\x0b\
+\x7f\x11\xe5\xf3t\x9b\xe0\xd4\xac\x82OQ\x9ai\x8c\xbc\
+\x9f3>\xebp\xb8\x1aZ\x88\xb4\x85f\x08\x11\x99\xc6\
+m\xf3$\xf4\x13\xb7}VA\xf6\x830\xf0\x04\x9b\x09\
+\x1e\x8dh\xe4_u\xbe\xf6\x89\x18\xcf\xa2\xa9Mv_\
+\xa1\xbf\x83\x06\x87~\x10-\xaf\xb9&\x93<$t\x8f\
+\xefO&\xec\xe6\xdf\xa7\xfe\x94\xb5\x8cH\xbaO\x00z\
+\x0b+\xff\x0afdQ\x190a\xab\xd8~F3\xc4\
+\x08'\x1db\x8b\xc3\x9c\x06}PY\xa2\x9a&\xa5\xcd\
+\x1aF\xfa\x08w\x09q\xab\xa2V\x1c*\xbc\xc4.m\
+S\x86\xff\x91\xbb\x9eQ\xdf3\xf1\xc2HG\x15\x0f\x13\
+\xfe\x11\xa3\x1d\xe9\x81k\x92\xbd\xea\xb6\x01&Nn(\
+\x0c\xc30\xe1\x11\x11\xb4\x83\x99\xad\x1e\xe3\x19\xd2T\x1b\
+\xe6\x89\xed!z\xda\xbe\xff4+\x07\xafhv\xd8\xd6\
+P\xfb;[\xd7\xb6\xe6\x0c\x92\xa8\x88\xfe\xcb\x9c{\x98\
+y}5k\x94\xa3]\x0e\xd4\x19\xb3\xbc\xc9\xc2\xaa\xf7\
+I\xe2\xcb\x8c\x86\xc2W\x80\x92M\xab\x16\x9a9\xa8&\
+\xcc2\x1e~T/\xc6P$\xc7krj\x1c>\xde\
+\xa9\x01\x8bQq\xbc\xd5y\x81\xd1&M\x8e\x8b\xa7\xfb\
+-_L=\xe9>8\x93\xee\x03\xdd\xf6\xa8'\xcb\x0c\
+u\xa6&\x8f\x22Y\x84\xc3\xa3H_\xa7\xd7a\xf6*\
+\xc8C\xd73\x1c\x10yW\xba\xbb\xcd\x9e\xf7k72\
+\x13\xc06m\xf3\x9bj\x08\xcb+\x83a\xa4x\xdfT\
+\xf5\x9fAc\x8c\xfa\x90~\x11\xbdj\xec\x03\xe4C\x8e\
+\xa5G:\x92\xfe\x0f\xb8\x91\xe8f\x18\x1by\xe3\xff\xf7\
+(\xfd\xbfGI4j\xda\xbe\x7fc\x8fR\x9d \x09\
+g\x899\x8afey\xf4\xeaM\xff'\x5cQ\xf1\x83\
+\x0e\xf8\xf8\x7f\x83\x07\xfeXlYg\x0e\xac\xf6#X\
+\xbe\xc7M\xbb\xd6\xd1\x13\x5c\xf1\xea\x9c\xaf\xb6\xe3\x875\
+\xa7\x02\xfd\xca\xbd\xecaf\x18\xe1[\x8d\xe3\x94\x96\xbb\
+u;a\xdb\xee\x96\xd1!\xe0\xf8)\xe9\xb0\xc7\xfaG\
+\xb5\xeb\xa9'q\x0fHi\x97\x18\xacm\x92\x16h\xf8\
+N\xb1=_\xa5\xdbx\x01'\x08t\x04d\xe4{1\
+\x0f\xb0\xbf%3\x91\x9eF>>\xd6\xca\xc1Q\x1aw\
+\xb5\xd8g\x96^\xd3\x1d\xff_\xb2,\xcd\x5c\xe7\xa7\x04\
+N$\xb0\xf6\xd0\xd6\xdf\xb04a\xb3[\xcc\xcfp\xf8\
+\xf9\x93f\xebU\xba\x08a\xc6\x0e=\xcf\x0cX\xd8k\
+\xb83\xcf\xad\x9b\xf2\x9f\x12\xbc\xf4m\xcbq\x89\xa8V\
+l\xdaa\x0e\xa3\xe29.\xbe;\xd9?x\xf6\xcd\x1f\
+\x9e;\xae\xd7\xe9\xf6\xfc\xfe`8:9=;\x1f\x7f\
+\x8f\xc1\xe2\xbe\x7fq\xf9\xfe\xae\xbc\xff<\xe5qW\xc1\
+\xb6H\xd1f\xc1\xa6\x98\xab\xb1\x0a\xd7\xe1\x08\xe3_\xfc\
+\x9b\xc3\xc3\xbe\x7fstuz>\xed\x94\xe1:\x88b\
+\xef\x9c\x9al\xb3X\x87\xd9\x83\xa5\x83\x09!2\xa8\x88\
+\xc6\x87Q=\x17\xa7C\x11\x07J9\x04#\x99D@\
+\x91Ny\x18_\xf5\x1br\x04\xa0\x95\xd9\x88G\x8e\x9a\
+\xa9\x0e\x88b8>\x17Y'\x954\x86\x8bz\xe6\x07\
+\x01\xea\xa8\x0a\xe0\x19\x08\xd4\x96B\x0bz,\x1c,\x07\
+\xect\xbcYm\xd8\xf8\xdc\xc0\xb2/i\xe8b\x90\x95\
+\xce\x0fq\xb8\xcd;\x8fa\x85\x9a\x18\xf6\x7f|\xf3\xea\
+\xdd\x7f\xbc\xfd\x0b\x1dA,D\xbbRG0V\x16\x8e\
+\xcaX\xcdD\xb8\xf0\x1ef\xea\xf0\xa9\xd8\x1f\x1b\xc9\x11\
+\xbe\x8b\xc6#F\x1c\xe5]\x95\xfe\x80\xd9\x00|\x0a\xa1\
+\xae\xb9\xd9D'\x0d\x18Y\x03c\x916\x80\x19\x01\x18\
+\xf2\xc4s\x8dR\x0b\x85\xd5n\x22\xd2\x0c\xa0\xa0\xac\xa5\
+\x1et\x00\x03\x01\x01\xf4\x98P\xf1\xc80DuyI\
+\x91TS\x19\xb1I_<(\x1d\xef_z\xe5\x85\xdf\
+\xf1;M\x00T\x0e\x14t\x00F\xa2hje\x00R\
+\xc7\xe1\x9a:\xad\xa3\xbc\x84\xe6\xa25\xaf\xbd\xac\x92\xa4\
+p\xd7\x1bw\xb0\x1fI\x8e\x00\xe8\xd4(V\xf4\xba\x95\
+\x0eU\x0b\xcf\xa2Ue\xa0\xbd\x87)\xc8;\xae\xc6\xf7\
+~:\xc6\x12\xff\x08\xa0\xdfs\xe0\x99\xc8@;\xee\xdd\
+\x1b\xfe\x1f\x1e\xc3f.E\x99F\xa2R\xbb\xce@\xea\
+@\x0a'\xef;0s\xb3\xcbR\xa0\xe0Qk\xa0g\
+\xb8\x0a\x19\x5cr\xed\x81QQ\xc3'\xa8\x8f\xa5\x81\x81\
++\x0eD\xa0\x160\xff\xf7\xb0\xff]w\xd0\x9f\xde\x1d\
+\xf5^\x1c\xdd\x0fOt\x97\xa8P\x0cx\x80\x1b\xecS\
+\xb7\x9d\xee\xf0l,z\xe9O\xbb\xee\xf7\x9e\x06AQ\
+\xd3\xdf\x00\xeda\xef\xe5\xd1\xbdQ\xcfs\xe8\x06Oj\
+\xe1a\xd4\xe0\xa4\x0f\x93\xa6\xe9\x93jQF\xfcV\x8a\
+\xed\x88_>xX\xd067l \x1a\xb0\x01\xc35\
+\xea^5dWV+\xadG\x93\x927\x07\xd5\x83 \
+\x9c\xc1\x7f*<\x94\x7f\xc5\xb0z\x0c\xaa\x17_+a\
+\xf5\xba\x0f~(6\x87W\x8b\xec\x7f\x5cv\x80\x19\xff\
+_\xa3\xbf}\x88FnS\xb3\x92\x19\x97\xefq=t\
+\xde\x1b\x1a\xab36\x84\x08\xd5\x22\xe7O\xc7=\xe5*\
+\x0ev+Rp\xf9\xe99\x81\x9f\xf3t\x99\x89\xebA\
+\xb9\xefZ;\x9b\xeb\x01\x08h\xd1\xb2^\x0a\xfd\x18\xd2\
+\xa0\xb3\xaa\x9aR\xaa\xa8\xc8\xc1\x22\x87\x8a0\x95\x8a\xca\
+|\x0f\x0b9:\xdf38\xdf Yu\xa9\x92\x99U\
+\x16\xbb\xaa\xc9\x0d\x94\xe2c\xb2\xa39\xf7\xca\x1aG\xeb\
+\x8c\x88\x8d\xa4B\x99(},q;\xd2\x0b\xb8=c\
+[2\x02AC\x82\x01\xafi\x8a\xe3\xde\x81\xa71\x9e\
+\xdb\xea\x83\x07t[\x9b\xd4\xf8\xcc\xff\xd5S*\x18>\
+\xdb\x9bM\xad\xbae\xdf\xa9\xa3\xd1{@\x03\x0e\x03\x03\
+\xdf{\x89\xe9\xb5\xad\xdf\x1d`\xaa\x837\xac\x86q\xb7\
+\xce@-\xd0Y\xd9\x07\x8d\x1d\xe4*\xfe\x19\xb7\xf2/\
+\xec\xc6\x8a\x81\xc6t\x83\x1dST\xcd\x05i\x98\x1di\
+\xcd\x9ab\xc8\xcb\xb4\xbf\xce\x99zx\xe9\xf6\xb9\x84\x7f\
+\xbd\xca\x88\xc9B\x15\xa3\xc5\xa5xUlJJ@\x1f\
+{#\x7f\xe8\x0f\xcb\xeb\xebk\x7f\xe0Y[\x83\x0f{\
+\x8d?\x80\xcd\x80\x94\x07\x98\xbc\x17\xa4\xa1k\x0ci\xd5\
+\xe35\xbe_\xce\xe4-\xdc\x88\xe73^\x8c\xf7\x07\xbd\
+\xd1I\xe7\xf2\xb3\xeb=\x9fvI\x1dQ\xc2e\xf9\x1c\
+w \xbd'uO\xbe\x01\xa5\xa4\xa0=LD\xddW\
+\xd9\xdc\xb4\xe9\x0f/>\x7f\xaeK\xd5\xe7\xcfFB\xa9\
+\xa5\xd9\xa1\xa4:\x92iI\x0c\x9c\x96\xb5\x14\x01\xaa\x10\
+\x1c\x1b\x0d\x87%\xf0\x0f\xffA\xae\xf9\x83\xb2\xbac\xfb\
+z\xcb\xf6}\xbei\x7f_\xd6c\xeek2\xd2e\xaf\
+QF\xfe\x0cG\xc0\x8f\xf9.\x89\x99q\x88\x06\xa1Q\
+\xa9)h\x22\x99\xe3\x9de\x86\xa4\xa0\xd5\x83#\xea\xd4\
+\x04\xa5\xce(\xcc\xd2!f=\xd4\xdc\x1a\x8e \x9e\xc2\
+\x99\xd9s\xf6*]o\xa2\xb8\x96\x02\xc0\xa1xl8\
+\xf9\x89+Q\xe4O\x8f\xf0\x97\x19\x02\xf4\xaf*\x95!\
+\xee\xd6\xca\xd2\xb5a\x82\xa1\x11\x99<\xb9\xcb+\x06U\
+\x0e\xfd\xf1\xdbn\xfe\xdd\xf5jM\x0d\x22M\x14*\xb7\
+`_\x13\xa73\x0a*\xe7\xe6w<\xef!\xc8\xb2\xe0\
+\x16T\xd4?\xb7Q\x06D\x07\xec=5{\x8f\xa7\xb6\
+M\x98\x15\xb7\x03\xee\x8e\xbf\x7fz\xdeBeK ,\
+_\x90\xb8\xc0\xa5\xaf%q\xc1\x92Pq\xba\xdf\x91\xb9\
+\xa0\x85\xcc\xf4\x04\xd4R\x17\x848\x19\xb9\x0b\x86\xe8T\
+\x905\xa50\xc0Qw\xc8e\x10\x0f\xbdf>\x83\x89\
+'\xdd\x16\x9bmQ\x8b\xb6o\x92L\xcb\xdf\x80\x93\xb8\
+C\x94O\xb4cC\x0c\x97wT\xcfx\xa8RZ'\
+Qg\x064\x12\xab\x89K\xa9\xceQ\xbe\x1e\xa4I\xc5\
+\xec\x1b\xe9\x00h\x22\xa9r4\x86\x8c\xd4\x80\xf6\xf8|\
+\xbe\xf7\xb4\xbb\xbdx\xfd\xd7\xb9Iq\x04]yG\xf5\
+\x90\x17N\x9eC\xdaI\x92\x10\xbfM\xca\x80t\xc2~\
+\xefX\xf7^<\x9e\x80\x8f\x9e/\x9au\x90,\xe3P\
+8'=\xc3s-Lu\x07w\xcb\x22\x1d\xa1{\xac\
+0B\x08jn}\x1b\xb7\xc0WCg\xa1\xb0\x19i\
++,d\x05yQzt9\xd0\xa3\x96\x0f:;\xc1\
+\x8e\xa8\x5c\xadq\xcd\x16%\xaf\xd1Nn\xf4\x14C\x9b\
+F\x1f1g\xb0\x0d\xab-\x04\x1d2sh\x85\xcb\xfc\
+>S%\xfa\xf9-g\xc7B'i\x91\xd7\xcd`8\
+\x0c\x1c\xfb&E\xf6\x88F\x06\xd8\x16\xd5\x1e\x1b/z\
+\x1a&]O\xfb\xef0\xfd\xd2\xb9\xd8\xec\xea\x0e\x1a\xef\
+E\x9a\x84dxq\x1a\xb0a\xc4/\xc0\x04\xb3j\xf7\
+\xc7\x02\xfc\x8c2\xa6*\x8cW{\x92\x85\xd3\x1f\x06\xe7\
+\x8f\xc6j\x05%\xde?Y\xbaL\xae\xb6\xc68\x8c\x9b\
+\xeb\xcd\x98\x856\x109\x00\x03tT\x11\xaf=]a\
+y\xf0\xdb\xc3^w\xe9\xc7\xaf\xd7\x8d\xed\xb3&\xe4\xb4\
+\x12\x10\xfb\x90\xed\x82\x7f\xc4\xf1\x9b\xde\x9fq\x0e\xa6\x1d\
+\xe1^\xcc\xbb\xee\x04\xbez.&Z\x1f\x0f9\xddt\
+\xda7\xee\xfb\xb0\x03:\xa87\xad\x19\xac0\xf6\x15\xa2\
+_\xf84\xa8\xea\xc5t\xe7\xca\x93\xb0\x8e\xd3\xb0\xf0j\
+\xcaA\x00W\xef\xe8\xf9\x15\xd4\x11]AQ<nU\
+\x1c\x05\xad\xf8\xcf\x008\xbf6\xa33.N\xe59\xc7\
+;\xa7k\xfdo\x8e\x9cf\xe9$C\x01g\x85GY\
+k\xbax\x04\x86i^\x08\xbf'ge\xcf\x1e@3\
+\xa4r\xa0\x10\xdd-\xb2`\xad\xb56\xf1\x14\xa7\xf2\x9e\
+p\xbd+I\xad\x8b\xaa\x804\xa4UQ\x0a\xa6zC\
+\xbaPu\xe3\xff\x12\xe9\x16\x92(\xb7\x14\x15?\xb4;\
+`F\xb4\xd2\xe6\xff\x84H\xb1\x22n\xac\x15\xb1O-\
+\x00;}\x18\xd0D\x18Sf\x18E\x87\xea\xe6\xf3\xd0\
+\xd8iT\x18(\xd6\x1b\xc1@\xb5\xf0\xcf\xe6\xdb\xf0]\
+\xabx\x97P\xd1:\xfb\x8a\xc9\xe7\xee\xa7v\xed\xc4\xeb\
+\x7f\x13\x83\xd2\xde\x089b\xd7\x18\x14\xb5\xfeV\xcd\xf0\
+\x0b\xf5\xc9\x9a\xf5\x07\xf7\xcap\xbd\xc3<^\xff\x0e#\
+\x09\xd7\xb5Q\xbcT\x14\xffQ}\xfa\xfa\x91\xfd\xde\x89\
+\xac\x8d\xa3C\xb4\xf8\xec\x81k\xec\x81\xc7S\xa1\x15E\
+V\xf5\xc3)<;\x92\x8df_\x99l\xd4H\xf5L\
+8\x0bv\x11\xb5\x08\xab6uC\xc8\xe0W\xc6j6\
+\x12\x07Xk\x12\xf3\x98\xf9\xff\xb2\x88\x87\xaf$\x16\xd1\
+\xba\xe6\xc1\x8aB\xf1oA\x22\x8a\x5c\x19{\xff\x0bB\
+\x17\x80x~\xa4o\xd8,\xcd\xc3:\xd9\xdbD\x89\x8c\
+y\xc0\x13\xc4\x98\xfeQ;J\xcd\xc9\xa1n&\xc5~\
+\x0fV\x037\xfe\xb5\xc7D\xb8\xda\xd0\x95\xf8\xf1a\xff\
+\x81\xd4\xd1\x92,\xad\xcc\x95'\xc10\x98\x065o\x81\
+\xac\xe0_\xc6\xd2B\xd5eH\x22\x8f>\xd3C\xad\xee\
+^<\x0e]\xbc\x0e4n?\xa9p\xff\x04k\x92Y\
+j:\xaa4\x8d\xd6\xc12\xb4\xdb\xda\xc7g\xdb\xfb\xf2\
+\xab\x96(\xf6.\x0b\x92\x1c\xdfQ\xa3\xc0\x86\x06\x9f\x93\
+f\xa4!\x88\xcd\x13\xacO@V\x1a\x09o\xe2\x99\xf3\
+\x7fb\x8b\x83\xdeJ\xfa\x8b _\x85<]@\xdb\x1b\
+\xfd~\x9f?9\xb1\xc5\x97g\x85\x83\x19\xe1\x936x\
+\x0d\xfeB\x83c\xdc\x08\xba\xc50 $\xae5r/\
+\xcaI\x9f\xf70t\xfd\xc9\xdd\x01\x1a\xe0\x0e!\xfa\xe6\
+\x88\xca\xff\xa4Q\xc9\x10\x16\x81\x8a=g\xc1&\xc5-\
+vS\xa7\xc6\xd1\xc4|W'f\x91ng\x0f\x13\xc3\
+\xfbG\x8a\x0eL\x8a\xe6u\x8a\x9a\xf1\x1dh\x1a\x16\x06\
+\xff\xe28\xda\xe45`\x7fp\xf7\xe2^58~\xe9\
+\xd8\xe2\xf33yzh\xdd\xed\x94\x17\xee\x11z\xac\xa8\
+p\xe8\x9a\x944\xba\xf8\x84+\xa0\x92\xa0O\x11\x9d\xca\
+\xab\xb7\xa2\x85\xd8\x9a`7_I$\x86\xb2\x8b\x84\xae\
+C\xea~\x0e\x8a\xd5\x00\x16\xc7\x22\x85}\x97\x9d\xb3\xc3\
+\xc1\x1f\x0d\x97\x0e\xb6vnP_\xceW`\xe4\xfe*\
+\x8c\xcf\x97\x86\xb2T\xfa\xddy\xfe\x8c\x03bp\xd0\x89\
+\xa3|\xdaUE*\xd8+\xbd\xef\x95+\x04\xe5\x94\xff\
+\xd2\x8b\x03\xe8S\xe20&\x09\xcd\x0dK/\xc2\xf7\x1e\
+=\xf9 u\xc8B\xcf\x985a\xabh\xb9\x8a1\xc0\
+X\xb2EOU3\x9c\x81\xdb\xe03\xb6\xd8\xe7\x81\xbb\
+\xe8\xdd\xe0_\xcf\xc8\xf6\xd2\xfc\x16\x94T\xectA;\
+1\xd0\xda\xa2\xf8\xf9\x02\xfb\x11\x18\x04\xab\x9d\xd3M\x16\
+\x9e\x9fb\xbbs\xfd\x84\x94+\xb1\x8f9F\xed~ \
+z\xb9\xc5\xa5\xa0\x9d\xd3!\xb5?\x1d\x22\xae\xdal\x1a\
+]\xc0\x92\x0c\xf2\xfc\xec@\x86\xc6Y\x5cA\xea\xde\x82\
+\xc2\x8enD\xad\xdcP\x88\xf3\xd4\xa7\xa8p\x0e\xce%\
+\x86\xc7SZ\xa5\x93\xf2\xbaP\xc8\x1aD\xc0zfP\
+\x09\x02\x7f_\xd0\xdc\xb8\x9dS\x0d\x89\x08\xa1\x13\xde\x8a\
+:\xb3\xebZ;\x13\xcfS\xaan\xf0\xbb\xd5\x0b\x16\xb4\
+\xb7\x16ohTt\x0a\x08\x15FJ\xf6X\x16\x5c\xb7\
+I+\xc5U\xff\xb4\xc8k\x22\xb1\xd2\x92@X\xf4L\
+\xb3h!g\xaf6\x7f\x1c\x9f1\x83\x08\x01\xdd\xb7\xe5\
+\x98L.\xfc\xeb)?5\xf7\x1dC\x9a\x0e\x0cAT\
+[\xa1\x98\xbfv\xca\xceE\x9e\x1eJ<\xe8\xf1h\x99\
+\xa4Y\xc8~\xfa1\xdf\xb3\x06&\x1bR\x13\xe9h\x95\
+\xb8\xad\xca\x9dS\x96\x99\xfc\xb6m9\x93%74\xb7\
+c\xec9\x1b\x92\x80\x8c\xe8\xf3N\xe4\xe2\x11]\x85\x1e\
+_H\xed\xc9D\xef\x1e\xcf\xea\xd6&\x1a=\x9ey\xc6\
+t\x8a\xb7\x93\xc6\xd4\xcdVgQQ\x93\xa0@\xac\xae\
+\x04\x04\xc5B\xc5\xa4Z\x8e<h\xe8:\xbc\x04&\x18\
+\x8d^\xaa\xc4\xe9p\xb4\x83J1\x928\x87=w5\
+r\xc14|\xa9\x11(\xe6<5\xe0vs\x14\x07-\
+\xf2n\xda\xedd\xe74\x8e*\x93\x06\x05\xbb\xd0R\xfa\
+\xdf,\xb5^\x0a\x13)\x81\x15\xccQ\x82WjJ\xad\
+\xc8\x84I\xe0\xa7\xf8xvp\xc0\x88\xb1\x8e\xd2'\x8b\
+(\xc7\x94 \xaa\xc2\xee\xce\x0ed\x7fJ\xc3\xb9\xcd\xf2\
+\xc0\x86\x15T\xe7\x98\x8a\xd9\xbc\x1f\x99/n\xee\xe2\xcd\
+\xa6\xc2\x9a\xcdN\xce\xf0\x5c)S\xed\xd0\xf2\xed\xd1\xe4\
+i\x8d\xc1\xbf\xd1\x84\xc2\xb6}J\xcf\xf5\x9e\xdbs,\
+\xcaN,\x95O\xe8\xcfe\xfc=\xc2\xd1\xbb\xbeF\x09\
+\xefO\xab\xe6j=b5j%\xbe\xdd\x03\xc2C\x9d\
+\xb5u'\x05F\xe8\xd9\x8c*2!\xa7\xa2Z\xf4\xb0\
+{U\x12z\xccbi\xc0\xdfcWq\xb0\xcck\x8b\
+\x92J\x85^\xc4Y/V4\xeb\xc5\xc2\x91v\x1c\x0f\
+;\xe4p\x94\xb3#\xceA\xd6\x0asx\xa6\x18_\x97\
+\x06\xac\xa9,G\x95&\xe7\xe6z\xc5^*\xa3m^\
+\x98\xe8\x8d\x83\xa3\x89\xd0\x82\xf20\xd5\xc4\x0e\xee;{\
+@\x229PE,ea+\xa7\x1fT\x02\xe1\xba\x82\
+\x12\x0b\xdaU\x80\xf0\x1e=\x80\x94[@\x16Z^\xd4\
+n$<y\x13\x98\xc1&\xc0\xb7\x80Y\xb6\x03\xf1\x22\
+\x8c\x1f \x16 *\xb4R\xc9\x0e\xedj\x1f\xf5\xed3\
+\xb5u\x04i\xbc\x92R!%\xd9\xad2@Q|\xb1\
+\x07\xc0\xbb\x08\x91U\xff\xf8\xe5't@\xa4\x09\x08\x99\
+\xbbM\x84\x1dF^l\xed\xa4\xb7\xb7\xff\xd1T\xa4}\
+\xe9z\xcbX\x10F,\x9b\xe3[#`\xebi\xf3\xb7\
+r&\x92\xe7\x0b\x1c\x01\x12\xa5\xdf\xc5\xf8\x10|\x0ax\
+\xe2\xef\xc8\xe1YA\x87x\x16\xb0\x81>\xcd\x1e\x04\xc1\
+G\x5cU\xf5\x03t\xdc7\xf1\x12\x9f\x87\xffGF\x16\
+\xfd~\x9aE\xcb(\xf9\x09poB\xfa\x99\x10\xa8\xe0\
+w\x97\x9c]\x02\xbdp\xbfda\x9e\xc6\x9f\xb0q#\
+\xc6\x1e\x13\xb7_\xbcc=G\xa2y\x98\x88\xd9\xe1\xc8\
+\xf5\x0c\xfc\xe1\xf8\x8f\xc4\xfe?\x88\xa8\xa2\x1a\x9b+\x83\
+\xbb\xb7\x0f\x9f\xa7\x01\xf5\xc0\xd5\x929\xdb\xdcj8\x91\
+L\xe0\xae\xa0;\xeb\xfc\xc7e\x8f7\xe5\xfe#\xd5\x06\
+{\x91`\x15!\x0f,\xad\xa6N\x89\x0d\x02O\xee\x9f\
+/\x91\xf8\xff\xa9Y2\xf9\x18\xad\x97\xe8c\xe4\xdc \
+l\xc8\x0cP\xfa\xc2\x14\x93\x0c\xf8Mx\xda\xac\x9a\x84\
+^z4\x7fE\xcc\xc3n\x97\xa6u\x86\x7f\x07\x05\xc6\
+9^\xc0\xe5,M\xe2[z\xce\x05[l\x03JD\
+,Xz\xc5\xcb0.\xaer\xea7\x11\xa1\xd6\xbd\xa7\
+m+IY\x12\x82\xbd\x86\xde\x0d\xfe\x83G\xf6\x1e\x96\
+\xef\xed\x99\x0d\x1bv\xb2\xb6z\xdc\x8e\xda\xea\xf4\xde\xd2\
+\x06A\xba\xbc\xad\xb2\xc2E\xd6\xca\xc6V\xe2\xb9bo\
+\xab}\xec*\x90\xfb\x8a\x0e<i\xefq\xc7\xae\xe78\
+\xa63\xfam\x90\x91\xf7MF\x80\xaa\x9865\xdc\xb7\
+\xf4\x83ZOy\x06\x9a\xe9t\xe6\xa7\xc7\x87\xd6b;\
+\xbf<\xe6\xb3\xadi\x0d\xb058\xf4\xbe\x1a\xa9H\xcc\
+\xb0\xc2\x139{\x06\xf4\xb3c\xb5\xc0\xc4ZD\x22\xff\
+u2\x11\x91X\xe1\xac\xb1\xa87\x06\xd2\x86g\x97\xa9\
+\xaf\xd7i\xba\xb1I0\xcf\x225b\x8c\x89k\x0d\x8b\
+\xc4+\x14\x11\x1ai\xf2\x81(\xc3\xd82@\x1aX1\
+\xc1\xec:*V,\xb0V;\xbeL\x07_\xb3`^\
+\xb0\xcd6\x0b\xa5w\xc0\xe8\xfa\x1d_Q\xd5\xeeIO\
+j\x1a\xe8\xab\x8e\x9f6)\xea\xb1;9w#Bc\
+\xab\x9b{\xb4T\xbc\x93\x9a\xa0\xf2g\x98?\x85\x19\xb7\
+Zl\x05OJU\xc4RR3|/\xc8\xf5*\xfa\
+[\x22tu\x88o\x8b;\xf4\xef8\xc8wZ5\xd6\
+f)\xa9h\xe7\xbam*W\x93\x955\x9cn\xdc\xda\
++\xdc\x9f\x22\xe0\xc1\x83\x1dn\xc2\xf0\xe3#:\xcc'\
+\xd6\xeb *[\x9b\xae\xa0\x0f\x1b$\xf1\x9d\xea8\xdf\
+%\x90\xef\x1a\xc7K\xbf}\xc7\x0f\xae\xba\xdb\x01\xd7o\
+\x95\x09A\xfa]o\xc0\x8fmx!E\xb9\xd2r\x82\
+\xf8Q\x97\xa7\x93K\xa7\x17\x9f\xc2\x816\x8d*\xe3\xb5\
+\xa3}\xe9\x14\xdd0\xbeW\xfcy\xc5\x9d\xbc\x05\xb2\xeb\
+c\xcbay\xa0\xa9f\x0e\x0c\x80\x95+\x1fl\x0e\xf9\
+<\xf5\xa8j\xab\xcaX\xa3{\x03t\x95\xd5\xe1lE\
+\xb6R\xd7\xd9V;\xf1Z\xc1C\x8d9\x98~\x17\xa2\
+\x81G\x9592C\x90t\x0d\xbd\x85\xa0k\xd4)\xa3\
+\xa2\x02\xdapz\x0dC\xa0\xa7\x9d\x1f\xa0\x1fa\xaa\xc8\
+\x1a\xe9#_uS\x85\xbc8\xa8\xf7\xcf_\x95\x19Y\
+\xe7*\xe120ox\x94 V\x0b#\xf3K\x96^\
+[\x0fa\x85\xfaaw\xfc\xf3\xc1|\xec\xd1p\xc0p\
+H+\xa6\xad\xfa\x22\x881\x16\xebA\x8e\xda\xc3 \x84\
+\xa9~\x87/\xdd'\xe6\xdb \xbb\xa5\x80w\x83/\xa3\
+\x98C\xb8S\xcf\xee\xa0o\xbf'\x1f\xd11\xda\xa9\xb7\
+O\xf4K'\x95\x87\xd9\xe5\xd3=-d\x02\x13]$\
+U\x07\x14\xee`\xc6\xceGR\xb8;\xaa\x0am\xbf]\
+Y\xe3\xbc\xe8\xee\x03\xef\xee\x03t\x07hT\x0f\x1f\xaa\
+\xcf\xb0<\x81\xdf\x8d\x1c\x07\xe4\x93\x0f\xd3\xca\xc3+\x9a\
+\xcb\x14\xc2\xd5\xce\xe6\x0f&\x9b\x0dF[\xcf\xb3H\x05\
+\xba\x9b\xd7\xf6\x0c5\xaeCjb{*\xeb\x0b\xaa\xf6\
+:\xae^[j\x01)\xfe\xd7\xb7e\xbe\x0bPXB\
+\xe5\x95Y\x93\xef\xd6\x90\xd4\xaem\x8d\xbbq\x04\x1a\xa5\
+\xdbB\xbe\xf1\xcck#\xe1\xbc\x80\xcc\x0b\xf5\xa8\xac1\
+-\xb5W`\x85\xfb\xdf\x86\xa2\xa2\xc71A=?\xf7\
+[\x0c\x1f\x915_h\xb40\xc2x\xf9o\xd74\xaa\
+s\xbc\xd0\xcf\x95'\x10\x9b\x05P:\xeb\xcd\x96\xf2r\
+\xa06\x9c\x87y\xa4\x9e\xfb\xdb\xc5(\xb1Q\x1bV\x86\
+1Q\x22\xfeE\xd94\xae\x1d\x02\xfe%\xccF\xa2Z\
+%\xad\xfa\xf6\xe2\x17/\x94\xc7\x8f\xff\xb7\xa2\x9d^\x09\
+\xd4\xf4\xe2\x9b:o~|\xc3\x8f72;K:\xbc\
+A8\xe8\x97\x08\xf1\x0dS\xf5\x22\xf5\xd9\xd1N\x83\x05\
+\xd0\xd7\xac\x87:\x15\xfa}\xa2\x07\x0c\x08\x05\xe86h\
+\xe0\x9a\x95\xd2`$\xa0\xa4<\xad\x0fC\x8a\xacx5\
+\xc3\x08\xfd[\x18o\xd05b\x1f\xcb\xa5+\x8f~#\
+\x91;\x0fk\xd7\xd1\x84Oy\x12\xf79\x14\xc8\xef\x10\
+\x93w\x9f\x8d\xfd\xeb\xee\x897\x5c\x82\xcc\x0e\x9f\x93\x8b\
+\xf1y\xb0\xde\x9c\xd4~:\xe7\x94\xd7\xc5E\xbd\xea\x9c\
+W-\x1b\xaax\xe0\xcesT\xa2\xf5J\x1eY\xf4\xfc\
+\xd9\x8b\xefN(F\xc7\x18\x97vI\xab\x0bv\x0c\xf8\
+\xb9\xd9\xc4\xd1<*\xe2[F/Z\xa33;\xa2\xe4\
+\xebUx\x03\xfb^\xb2`\x09\x88\xce\x82\xfd\xed\xdd\xcf\
+\xaf\x81\x1d \x95Q\x98\xdb\xdc\xd0\xdd?w\x9f\xe1k\
+\x07\x8b\xae\x87O-<\xbb\x99\x1c\xf6\xbf\xfb\xa1\xff\xaf\
+A\xffj\xca\x8b\x8030\x1d\xe3a\x04d*\x9b\xfe\
+\xb2\xc7\x12\xb9h\xc8\xc7\xd1\xe4\x05G\x15\x97p\xbd1\
+O\xe3\x14\x9f\xef\x93\x06\xfd\xc81@\x8c\xd8:\x02~\
+f\xacG\xd1@\xc1\xc8w\xb2n\xb4\x16\x1a3\x1e\xb9\
+3\xb8\xca\xd2\xf5+\x11\x08\xe4\x920\xfd\x94\x14\x80^\
+\x87p\x1e{=v\xf4\xd23\x93Q\x9a\xdav\x13+\
+\x18\xdd\x92n\xebHr_\x990JE\xce0\xc3\x98\
+\xfc\x1dR\x08\x97\xf4\xc3O\xf4\xaf\xfc\xe9V8>\xd2\
+w\xc4\x02\x90\xdc\x1f\xc4\x7f\x82\xc5\xf0}H\xa76\xcd\
+\xd5H\xb3\x1f\xe7\xb7\x07;j\xec\x19F8\xdad\xf0\
+\xb7\xd1\xc3\xa7@\xbd\xa3\xa7\xab\xad\xf0\xb4\x0b\x7f2\xf5\
+\xfc\x0b\x11\x8e\xa63\x01l\x9a\xd5+\xda\xaa\xdb\x93\xfa\
+\xb2\x16L\xe2\xdb\xb8L\xb4\x1eU\x8f\x81\xe6\x9c\xea7\
+\xb9\x0d\x96\x19\x0b\xdf\xe6\xad\xe1\xadF\x07\xb5\xf0N\xeb\
+@4\xe1\xb5\xce'\x0e\x85\xc0\xe2W\xf5\xcc'\xac\x9a\
+\xeb\x90~\xcbSDY\xa0\xe3\xe3\x16\xa8\xe6\x8fi#\
+,\x0b\xae\x0a\xf1Lw\x1c\xe4\x05\xcb\xe1\xef\x15zu\
+\xa3\x22\x07e\x0c\xc7\xfe\xb9\xbc*\xeaI\x9c\xb3m\x81\
+x\xd7\x18\x18\xc5\x9d\xb9E\xca\x82\xc5\x82]\x16\xab\xa0\
+\xb8\x94`\x94\x89?\x1a\x0e\x8b4\x05\xc3;\x0a\x8b+\
+\xb0y\x96C\x5c\x87\xc3\xecj\xfe\xe2\xbb?\xbd|&\
+\xde\x90\xea\xbfP\xcb\x02\x7fPz4\xed\x8e\xfcag\
+r1\x9c\xaa'\x18\x91X#R\xb9i\xd80q4\
+\xa4.s\x86\xf2\xe8l\xa7\xf9\xeche\xdewQ\xb7\
+\xc6\x13\xca\xf2\xaa\x88\xb0\x9f5bQY\xe4\x94\x07\xa4\
+\xde\xad;\x16\xebv8T\x8b[\x88\x81\xdd\xe7H\xa4\
+\x09a\xaf#z\x06\x1c\xf1\xf0\xcb\x1e\x95LG\xa8+\
+J\xe3\x01\xbc\xae\xe2\xa3g\xf4@Q\xc9\xb5.jh\
+L\x10\x90Ir\xd3\x88\xa13\xfe\xfb~\xdc\xb8\xad_\
+\xbeP6\xd87\xe5\x05\xff5v\xf8K<\xa8\xd4\x19\
+A\xd9\xf8\xd9t\x18\x9d\x18\x22\x8eOA\xf1\x0b\x02\xfc\
+Dq\xe9\xa8[\xe1\xb3\x09\xc5\xdd\x80\xe9\xec\x83\x91\xdc\
+\x0d`G\xfaG\xce\xa0^\x1d\xf6?\x86\xb7\xd5 J\
+\xa8\xdf\xe23@\x8d\x87@\xde\x1a\xf0)(:\xfca\
+\x15\xa1\x00|\xf4\xfe<\x81\xd9\x89\x93of\x1f@\x8a\
+\xcd\xa0\xa3 \x7fs\x9d\xbc\x95O\x01\xcc\x038\xdc\x09\
+\xea\x90.+\xdf\x11\xc63\x812J\xb1%\x10\xfaf\
+\x9f\xb1j\xe1\xeb\xb3\x0f\xb6\x8e0\x1e\xf4\xa4\x83\xd7/\
+\xe9u\x8f\xd1\xfb\xec\x9aWt\xa0\xa5^8\x80!#\
+\xf8\xcb\x93\xa0\x0eK\xa1\x0eYi<e\xdb\xb5\x9f\xb2\
+\xd5?\x01Ay\x05\xc6\x91\x9a\x9d\x9b=\x8a'o\x09\
+\xcd\x1cc\xfc\xb0\xa6&k\xc2T\xb5\xd0\x9cJ4\xbc\
+\x94^Ht\x8c\xd7\x14\x8c\x09m=\xd1\x1bo\x9d\xd6\
+\x9f3\xf5}1\xd2Rb\xd51\xef\x08l\xba\xfc\x7f\
+\xa6\x1b\x8a\x8a\xd9\xc5\xaf-\x94w\x1f_p\x8ec\xcc\
+\x89V\xa6\x0a\xcfY\x081_\x01\x85\x86,CP\xa9\
+\x09N\x15\xed\xae\x8c\x82\x98\x04\x17Qf\xa0\x96r/\
+pI\xab\xf7\xe2\xe9\x01lY\x9c\xd0\x8f\xc6\x0ba\xad\
+\xe6D\x08\x92\xbc\x11GlX\xd1Q\xae\xdf\x9f\xa7\xc7\
+\xff\x01\x8d\x1ew\xa5\xfb}\xfe\xca6R\xe8|Y_\
+x\x1d\x88\x87\x0d\x1d\xfdW[\x1d2\xe2\x98\xaf\x0b\xba\
+\x96\x00\x85\xdd\x93\x9c\x013\x0f\xe0a\xf0\xf6\x03\x1a\x92\
+\xc7\xc8\x12A1\xd9\x0fH\xb1\x9c\x19E3m\x8c\xf6\
+\xac\x90XH\x1cdu\xc85\xc6\xcd\x10\xfd\xf8\xae8\
+\xee\xf0b}\xf7P\xb9\xac\xeaI\xc3\xe5N\xbd\xf2N\
+\xfe@\x190\xcc\xfb\xd0\x01\xc4\x86\xaf\x91\xbc\xe4\xc6\xbb\
+\xe9\xa05#\xd3\x19i\xfe\xcc\x8a\x19\xc7\xa1\xee0\xd4\
+\xcf{*\x19\xf4\xc4f\xd7\x1a~!\x87\xee\x86\x9e5\
+J\xd19\xea\x05\xd3\xd7o\x8ch\x91&\xd6=\x12H\
+\xb5\xad\xfe\xac\x02f\x8f\x1a\x1a\xaa\xcf\xda\xe1T\xa3\x09\
+0\xd4\x0e\x9b\xe2ZF\xff\xec\x8e\xc1\x09&.l\xcc\
+\x8b7W\xf0\xd5\xb0\xa6\x9a9\xc2pab\xb4E\xad\
+\xcb\x1d\xa4\xdb\xdc\x04\x04\x86\x19n\x8d\xc30\xb1U9\
+O\xde\x07\x92%\xefOt\xae\xd3\xbe\xee\x10\x84I}\
+\xd1\xda\xf0Em.qF\x5c{\x1a\x17a\x0c+\xd0\
+\x1e\x80\xd9\x89\x98f\xaf\x8aB\xefo\xc2]j\x09\x81\
+\xed$uu\x0c\x01B\x99\x0c\xe5\x8f\x10+\x97\xc9>\
+?\xfa,\xc2\xca\xeb\x09\xa2\xf3~_J\x1d\x8c\xd8\x1c\
+\x0c\x1fP\x05Z\x87\xe0\x1b\x1e}f8\xf1-\xd1\xec\
+U\x82\xef-!5\x07o\xca\x9c\x04\xe21\xf9\x22\x9e\
+\x1fs-\xf9w\xb16\x06\xfa\x1a\xfeiC2\x07\xc5\
+\x0c\x5c\x8cg\x0a\x9c\xd4*\x9b3\x06\xf0\xcfn\xd6i\
+\xb1\xf7\xc4j@\x9f\xbc%(\x9c^\xa9\xfe\xf5\x9a\xa2\
+l\x06<\xb9=F\xed\x09\x94b\xec\xd6\x22lPM\
+\xbaI}5\x86\x83u\x98\xe7\x18\xfb\xc0o\xed\xde\xc6\
+!\xee\x95\xb0W\xa7YA\xc7+<Z\xc8\x83\xc42\
+*V\xdb\xd9\x00\x0e$CN\xd6\x87\x5c|\x18\x18\xe7\
+jW(\xe6\x0a\xe5`\xd2\x80\xb1\x91\x14\xb5\xe5\x84\xf1\
+\xb8?$b\xb7N\xe7\xf4Cj\x8b\x11F\xe5R:\
+\x84>p\xab\xd8)\x83j\xbc\xe212\x1e8\x98c\
+\xe4W\xc8\xc9\x17&\x81\xb4\xa5\xa5\x81\xf1fc\xa4\xae\
+\x09\x8aU$\x82,\xc8\xc3\xe2\x8d\x8aNP\xd2\xae\x0e\
+\xda|\xb2Z'J\x0c\x93\xd7\xf3\xabM\x01\x0b\xd6\xe6\
+\x8f\x02\xdc\x0an\xb1\x9cH\x9c[\xc2\xee\xe7i\x82|\
+\xab\xe2\xafG\xc9{\x07~\xfc\xbdZ\x8b\xdb\x1e\xfa\xaa\
+\xf2\x16\xea\x85<\xf9`\xa4n\xc8\xd4*7{\xd0y\
+&\xf8\xe3*\xf0e\x0b<\xef\x8b\x16<\xe7\xca\xc4,\
+\x9f\xa6\xb0H\xb2c\x04t|\x00\xaf\x95a\x93V\x13\
+\xf5\xbc\x87I\x0b\x17\x1e\x1bN\xfd\xceT\xbd\x98\xe7\x0d\
+Z\xe5\xfcw\xc2M\x82)\xa8K\xc0\xec\xc9\x83\x7fe\
+\x1eq1\xd6f\xcb|\xa5\x92?\xbee\x09\xd0[\x19\
+a\xc2?\x9c\xc8\xf2\x8d]\xce\xbfkqP\xd1\x1bg\
+\x8aO\xaa\xa9\x15\xddqf\xc5Zh\x04\xaf\xc5o\x89\
+\xd3\xbf\xaail\x96\xe27\xdd\xc0\x0c#93\xe3@\
+T\xe3\xa8\x0dB\xb8d5.\x19\xf0\x22\xe5|\xcf\xb0\
+m\xd7\xe9b\x1b\x8b}\xc9\xb0\xae\x9f?\x97\xa6$\x18\
+\x9d\xa0prnN\xa6d\xb1\x8a\xbd\x8b7\x1d(\x00\
+\xbd\x8e\x8cGn8\x12\x8e\xb7b\x92b'\xbcb\x10\
+\xacE*\x03\xff\xee\x9a.\xa2\xca*\xe5\x1e6}T\
+\xca\xd2\xb4\x18\xf0:\x93\x84=T\xf4\xa8$\xf1E\x0d\
+A\xc5u\x94,@\xd3T\xc7:\x96\x15#\xb6\x8c\xd3\
+Y\x10\xa3{\xef\xbf\x01\x9e\xed\xc5)\
+"
+
+qt_resource_name = b"\
+\x00\x0a\
+\x08\xce\x22\xb4\
+\x00d\
+\x00e\x00f\x00a\x00u\x00l\x00t\x00.\x00m\x00d\
+\x00\x08\
+\x08\xb6\x8e\xf9\
+\x003\
+\x00r\x00d\x00p\x00a\x00r\x00t\x00y\
+\x00\x0a\
+\x0c\xba\xf2|\
+\x00i\
+\x00n\x00d\x00e\x00x\x00.\x00h\x00t\x00m\x00l\
+\x00\x0c\
+\x08\xd0i\xc3\
+\x00m\
+\x00a\x00r\x00k\x00d\x00o\x00w\x00n\x00.\x00c\x00s\x00s\
+\x00\x09\
+\x09\x1b\x92\x13\
+\x00m\
+\x00a\x00r\x00k\x00e\x00d\x00.\x00j\x00s\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01z.[\x95V\
+\x00\x00\x000\x00\x00\x00\x00\x00\x01\x00\x00\x01\xe1\
+\x00\x00\x01z.[\x95V\
+\x00\x00\x00J\x00\x01\x00\x00\x00\x01\x00\x00\x04\x97\
+\x00\x00\x01z.[\x95V\
+\x00\x00\x00h\x00\x01\x00\x00\x00\x01\x00\x00\x0a\xf1\
+\x00\x00\x01z.[\x95V\
+"
+
+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/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt
new file mode 100644
index 000000000..23c52cc43
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt
@@ -0,0 +1,16 @@
+Copyright 2011 Kevin Burke unless otherwise noted.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Some content is copyrighted by Twitter, Inc., and also released under an
+Apache License; these sections are noted in the source.
diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt
new file mode 100644
index 000000000..8e3ba0e0a
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css b/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css
new file mode 100644
index 000000000..24fc2ffe2
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css
@@ -0,0 +1,260 @@
+body{
+ margin: 0 auto;
+ font-family: Georgia, Palatino, serif;
+ color: #444444;
+ line-height: 1;
+ max-width: 960px;
+ padding: 30px;
+}
+h1, h2, h3, h4 {
+ color: #111111;
+ font-weight: 400;
+}
+h1, h2, h3, h4, h5, p {
+ margin-bottom: 24px;
+ padding: 0;
+}
+h1 {
+ font-size: 48px;
+}
+h2 {
+ font-size: 36px;
+ /* The bottom margin is small. It's designed to be used with gray meta text
+ * below a post title. */
+ margin: 24px 0 6px;
+}
+h3 {
+ font-size: 24px;
+}
+h4 {
+ font-size: 21px;
+}
+h5 {
+ font-size: 18px;
+}
+a {
+ color: #0099ff;
+ margin: 0;
+ padding: 0;
+ vertical-align: baseline;
+}
+a:hover {
+ text-decoration: none;
+ color: #ff6600;
+}
+a:visited {
+ color: purple;
+}
+ul, ol {
+ padding: 0;
+ margin: 0;
+}
+li {
+ line-height: 24px;
+}
+li ul, li ul {
+ margin-left: 24px;
+}
+p, ul, ol {
+ font-size: 16px;
+ line-height: 24px;
+ max-width: 540px;
+}
+pre {
+ padding: 0px 24px;
+ max-width: 800px;
+ white-space: pre-wrap;
+}
+code {
+ font-family: Consolas, Monaco, Andale Mono, monospace;
+ line-height: 1.5;
+ font-size: 13px;
+}
+aside {
+ display: block;
+ float: right;
+ width: 390px;
+}
+blockquote {
+ border-left:.5em solid #eee;
+ padding: 0 2em;
+ margin-left:0;
+ max-width: 476px;
+}
+blockquote cite {
+ font-size:14px;
+ line-height:20px;
+ color:#bfbfbf;
+}
+blockquote cite:before {
+ content: '\2014 \00A0';
+}
+
+blockquote p {
+ color: #666;
+ max-width: 460px;
+}
+hr {
+ width: 540px;
+ text-align: left;
+ margin: 0 auto 0 0;
+ color: #999;
+}
+
+/* Code below this line is copyright Twitter Inc. */
+
+button,
+input,
+select,
+textarea {
+ font-size: 100%;
+ margin: 0;
+ vertical-align: baseline;
+ *vertical-align: middle;
+}
+button, input {
+ line-height: normal;
+ *overflow: visible;
+}
+button::-moz-focus-inner, input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+input[type=checkbox], input[type=radio] {
+ cursor: pointer;
+}
+/* override default chrome & firefox settings */
+input:not([type="image"]), textarea {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+input[type="search"] {
+ -webkit-appearance: textfield;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+label,
+input,
+select,
+textarea {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: normal;
+ margin-bottom: 18px;
+}
+input[type=checkbox], input[type=radio] {
+ cursor: pointer;
+ margin-bottom: 0;
+}
+input[type=text],
+input[type=password],
+textarea,
+select {
+ display: inline-block;
+ width: 210px;
+ padding: 4px;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 18px;
+ height: 18px;
+ color: #808080;
+ border: 1px solid #ccc;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+select, input[type=file] {
+ height: 27px;
+ line-height: 27px;
+}
+textarea {
+ height: auto;
+}
+
+/* grey out placeholders */
+:-moz-placeholder {
+ color: #bfbfbf;
+}
+::-webkit-input-placeholder {
+ color: #bfbfbf;
+}
+
+input[type=text],
+input[type=password],
+select,
+textarea {
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+input[type=text]:focus, input[type=password]:focus, textarea:focus {
+ outline: none;
+ border-color: rgba(82, 168, 236, 0.8);
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+/* buttons */
+button {
+ display: inline-block;
+ padding: 4px 14px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ background-color: #0064cd;
+ background-repeat: repeat-x;
+ background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));
+ background-image: -moz-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -ms-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));
+ background-image: -webkit-linear-gradient(top, #049cdb, #0064cd);
+ background-image: -o-linear-gradient(top, #049cdb, #0064cd);
+ background-image: linear-gradient(top, #049cdb, #0064cd);
+ color: #fff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ border: 1px solid #004b9a;
+ border-bottom-color: #003f81;
+ -webkit-transition: 0.1s linear all;
+ -moz-transition: 0.1s linear all;
+ transition: 0.1s linear all;
+ border-color: #0064cd #0064cd #003f81;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+}
+button:hover {
+ color: #fff;
+ background-position: 0 -15px;
+ text-decoration: none;
+}
+button:active {
+ -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+button::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js b/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js
new file mode 100644
index 000000000..33c02d9cf
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js
@@ -0,0 +1,1514 @@
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/markedjs/marked
+ */
+
+;(function(root) {
+'use strict';
+
+/**
+ * Block-Level Grammar
+ */
+
+var block = {
+ newline: /^\n+/,
+ code: /^( {4}[^\n]+\n*)+/,
+ fences: noop,
+ hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
+ heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,
+ nptable: noop,
+ blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
+ list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
+ html: '^ {0,3}(?:' // optional indentation
+ + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
+ + '|comment[^\\n]*(\\n+|$)' // (2)
+ + '|<\\?[\\s\\S]*?\\?>\\n*' // (3)
+ + '|<![A-Z][\\s\\S]*?>\\n*' // (4)
+ + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>\\n*' // (5)
+ + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)' // (6)
+ + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag
+ + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag
+ + ')',
+ def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,
+ table: noop,
+ lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
+ paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/,
+ text: /^[^\n]+/
+};
+
+block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/;
+block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/;
+block.def = edit(block.def)
+ .replace('label', block._label)
+ .replace('title', block._title)
+ .getRegex();
+
+block.bullet = /(?:[*+-]|\d+\.)/;
+block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
+block.item = edit(block.item, 'gm')
+ .replace(/bull/g, block.bullet)
+ .getRegex();
+
+block.list = edit(block.list)
+ .replace(/bull/g, block.bullet)
+ .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))')
+ .replace('def', '\\n+(?=' + block.def.source + ')')
+ .getRegex();
+
+block._tag = 'address|article|aside|base|basefont|blockquote|body|caption'
+ + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption'
+ + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe'
+ + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option'
+ + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr'
+ + '|track|ul';
+block._comment = /<!--(?!-?>)[\s\S]*?-->/;
+block.html = edit(block.html, 'i')
+ .replace('comment', block._comment)
+ .replace('tag', block._tag)
+ .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
+ .getRegex();
+
+block.paragraph = edit(block.paragraph)
+ .replace('hr', block.hr)
+ .replace('heading', block.heading)
+ .replace('lheading', block.lheading)
+ .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks
+ .getRegex();
+
+block.blockquote = edit(block.blockquote)
+ .replace('paragraph', block.paragraph)
+ .getRegex();
+
+/**
+ * Normal Block Grammar
+ */
+
+block.normal = merge({}, block);
+
+/**
+ * GFM Block Grammar
+ */
+
+block.gfm = merge({}, block.normal, {
+ fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/,
+ paragraph: /^/,
+ heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
+});
+
+block.gfm.paragraph = edit(block.paragraph)
+ .replace('(?!', '(?!'
+ + block.gfm.fences.source.replace('\\1', '\\2') + '|'
+ + block.list.source.replace('\\1', '\\3') + '|')
+ .getRegex();
+
+/**
+ * GFM + Tables Block Grammar
+ */
+
+block.tables = merge({}, block.gfm, {
+ nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/,
+ table: /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/
+});
+
+/**
+ * Pedantic grammar
+ */
+
+block.pedantic = merge({}, block.normal, {
+ html: edit(
+ '^ *(?:comment *(?:\\n|\\s*$)'
+ + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
+ + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))')
+ .replace('comment', block._comment)
+ .replace(/tag/g, '(?!(?:'
+ + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub'
+ + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)'
+ + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
+ .getRegex(),
+ def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/
+});
+
+/**
+ * Block Lexer
+ */
+
+function Lexer(options) {
+ this.tokens = [];
+ this.tokens.links = {};
+ this.options = options || marked.defaults;
+ this.rules = block.normal;
+
+ if (this.options.pedantic) {
+ this.rules = block.pedantic;
+ } else if (this.options.gfm) {
+ if (this.options.tables) {
+ this.rules = block.tables;
+ } else {
+ this.rules = block.gfm;
+ }
+ }
+}
+
+/**
+ * Expose Block Rules
+ */
+
+Lexer.rules = block;
+
+/**
+ * Static Lex Method
+ */
+
+Lexer.lex = function(src, options) {
+ var lexer = new Lexer(options);
+ return lexer.lex(src);
+};
+
+/**
+ * Preprocessing
+ */
+
+Lexer.prototype.lex = function(src) {
+ src = src
+ .replace(/\r\n|\r/g, '\n')
+ .replace(/\t/g, ' ')
+ .replace(/\u00a0/g, ' ')
+ .replace(/\u2424/g, '\n');
+
+ return this.token(src, true);
+};
+
+/**
+ * Lexing
+ */
+
+Lexer.prototype.token = function(src, top) {
+ src = src.replace(/^ +$/gm, '');
+ var next,
+ loose,
+ cap,
+ bull,
+ b,
+ item,
+ space,
+ i,
+ tag,
+ l,
+ isordered,
+ istask,
+ ischecked;
+
+ while (src) {
+ // newline
+ if (cap = this.rules.newline.exec(src)) {
+ src = src.substring(cap[0].length);
+ if (cap[0].length > 1) {
+ this.tokens.push({
+ type: 'space'
+ });
+ }
+ }
+
+ // code
+ if (cap = this.rules.code.exec(src)) {
+ src = src.substring(cap[0].length);
+ cap = cap[0].replace(/^ {4}/gm, '');
+ this.tokens.push({
+ type: 'code',
+ text: !this.options.pedantic
+ ? cap.replace(/\n+$/, '')
+ : cap
+ });
+ continue;
+ }
+
+ // fences (gfm)
+ if (cap = this.rules.fences.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'code',
+ lang: cap[2],
+ text: cap[3] || ''
+ });
+ continue;
+ }
+
+ // heading
+ if (cap = this.rules.heading.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'heading',
+ depth: cap[1].length,
+ text: cap[2]
+ });
+ continue;
+ }
+
+ // table no leading pipe (gfm)
+ if (top && (cap = this.rules.nptable.exec(src))) {
+ item = {
+ type: 'table',
+ header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
+ };
+
+ if (item.header.length === item.align.length) {
+ src = src.substring(cap[0].length);
+
+ for (i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (i = 0; i < item.cells.length; i++) {
+ item.cells[i] = splitCells(item.cells[i], item.header.length);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+ }
+
+ // hr
+ if (cap = this.rules.hr.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'hr'
+ });
+ continue;
+ }
+
+ // blockquote
+ if (cap = this.rules.blockquote.exec(src)) {
+ src = src.substring(cap[0].length);
+
+ this.tokens.push({
+ type: 'blockquote_start'
+ });
+
+ cap = cap[0].replace(/^ *> ?/gm, '');
+
+ // Pass `top` to keep the current
+ // "toplevel" state. This is exactly
+ // how markdown.pl works.
+ this.token(cap, top);
+
+ this.tokens.push({
+ type: 'blockquote_end'
+ });
+
+ continue;
+ }
+
+ // list
+ if (cap = this.rules.list.exec(src)) {
+ src = src.substring(cap[0].length);
+ bull = cap[2];
+ isordered = bull.length > 1;
+
+ this.tokens.push({
+ type: 'list_start',
+ ordered: isordered,
+ start: isordered ? +bull : ''
+ });
+
+ // Get each top-level item.
+ cap = cap[0].match(this.rules.item);
+
+ next = false;
+ l = cap.length;
+ i = 0;
+
+ for (; i < l; i++) {
+ item = cap[i];
+
+ // Remove the list item's bullet
+ // so it is seen as the next token.
+ space = item.length;
+ item = item.replace(/^ *([*+-]|\d+\.) +/, '');
+
+ // Outdent whatever the
+ // list item contains. Hacky.
+ if (~item.indexOf('\n ')) {
+ space -= item.length;
+ item = !this.options.pedantic
+ ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
+ : item.replace(/^ {1,4}/gm, '');
+ }
+
+ // Determine whether the next list item belongs here.
+ // Backpedal if it does not belong in this list.
+ if (this.options.smartLists && i !== l - 1) {
+ b = block.bullet.exec(cap[i + 1])[0];
+ if (bull !== b && !(bull.length > 1 && b.length > 1)) {
+ src = cap.slice(i + 1).join('\n') + src;
+ i = l - 1;
+ }
+ }
+
+ // Determine whether item is loose or not.
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+ // for discount behavior.
+ loose = next || /\n\n(?!\s*$)/.test(item);
+ if (i !== l - 1) {
+ next = item.charAt(item.length - 1) === '\n';
+ if (!loose) loose = next;
+ }
+
+ // Check for task list items
+ istask = /^\[[ xX]\] /.test(item);
+ ischecked = undefined;
+ if (istask) {
+ ischecked = item[1] !== ' ';
+ item = item.replace(/^\[[ xX]\] +/, '');
+ }
+
+ this.tokens.push({
+ type: loose
+ ? 'loose_item_start'
+ : 'list_item_start',
+ task: istask,
+ checked: ischecked
+ });
+
+ // Recurse.
+ this.token(item, false);
+
+ this.tokens.push({
+ type: 'list_item_end'
+ });
+ }
+
+ this.tokens.push({
+ type: 'list_end'
+ });
+
+ continue;
+ }
+
+ // html
+ if (cap = this.rules.html.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: this.options.sanitize
+ ? 'paragraph'
+ : 'html',
+ pre: !this.options.sanitizer
+ && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
+ text: cap[0]
+ });
+ continue;
+ }
+
+ // def
+ if (top && (cap = this.rules.def.exec(src))) {
+ src = src.substring(cap[0].length);
+ if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
+ tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
+ if (!this.tokens.links[tag]) {
+ this.tokens.links[tag] = {
+ href: cap[2],
+ title: cap[3]
+ };
+ }
+ continue;
+ }
+
+ // table (gfm)
+ if (top && (cap = this.rules.table.exec(src))) {
+ item = {
+ type: 'table',
+ header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : []
+ };
+
+ if (item.header.length === item.align.length) {
+ src = src.substring(cap[0].length);
+
+ for (i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (i = 0; i < item.cells.length; i++) {
+ item.cells[i] = splitCells(
+ item.cells[i].replace(/^ *\| *| *\| *$/g, ''),
+ item.header.length);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+ }
+
+ // lheading
+ if (cap = this.rules.lheading.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'heading',
+ depth: cap[2] === '=' ? 1 : 2,
+ text: cap[1]
+ });
+ continue;
+ }
+
+ // top-level paragraph
+ if (top && (cap = this.rules.paragraph.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'paragraph',
+ text: cap[1].charAt(cap[1].length - 1) === '\n'
+ ? cap[1].slice(0, -1)
+ : cap[1]
+ });
+ continue;
+ }
+
+ // text
+ if (cap = this.rules.text.exec(src)) {
+ // Top-level should never reach here.
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'text',
+ text: cap[0]
+ });
+ continue;
+ }
+
+ if (src) {
+ throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
+ }
+ }
+
+ return this.tokens;
+};
+
+/**
+ * Inline-Level Grammar
+ */
+
+var inline = {
+ escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,
+ autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/,
+ url: noop,
+ tag: '^comment'
+ + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
+ + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
+ + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
+ + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
+ + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section
+ link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/,
+ reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,
+ nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,
+ strong: /^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/,
+ em: /^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/,
+ code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/,
+ br: /^ {2,}\n(?!\s*$)/,
+ del: noop,
+ text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/
+};
+
+inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g;
+
+inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/;
+inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/;
+inline.autolink = edit(inline.autolink)
+ .replace('scheme', inline._scheme)
+ .replace('email', inline._email)
+ .getRegex();
+
+inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/;
+
+inline.tag = edit(inline.tag)
+ .replace('comment', block._comment)
+ .replace('attribute', inline._attribute)
+ .getRegex();
+
+inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/;
+inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/;
+inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/;
+
+inline.link = edit(inline.link)
+ .replace('label', inline._label)
+ .replace('href', inline._href)
+ .replace('title', inline._title)
+ .getRegex();
+
+inline.reflink = edit(inline.reflink)
+ .replace('label', inline._label)
+ .getRegex();
+
+/**
+ * Normal Inline Grammar
+ */
+
+inline.normal = merge({}, inline);
+
+/**
+ * Pedantic Inline Grammar
+ */
+
+inline.pedantic = merge({}, inline.normal, {
+ strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
+ em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
+ link: edit(/^!?\[(label)\]\((.*?)\)/)
+ .replace('label', inline._label)
+ .getRegex(),
+ reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
+ .replace('label', inline._label)
+ .getRegex()
+});
+
+/**
+ * GFM Inline Grammar
+ */
+
+inline.gfm = merge({}, inline.normal, {
+ escape: edit(inline.escape).replace('])', '~|])').getRegex(),
+ url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/)
+ .replace('email', inline._email)
+ .getRegex(),
+ _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
+ del: /^~~(?=\S)([\s\S]*?\S)~~/,
+ text: edit(inline.text)
+ .replace(']|', '~]|')
+ .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|')
+ .getRegex()
+});
+
+/**
+ * GFM + Line Breaks Inline Grammar
+ */
+
+inline.breaks = merge({}, inline.gfm, {
+ br: edit(inline.br).replace('{2,}', '*').getRegex(),
+ text: edit(inline.gfm.text).replace('{2,}', '*').getRegex()
+});
+
+/**
+ * Inline Lexer & Compiler
+ */
+
+function InlineLexer(links, options) {
+ this.options = options || marked.defaults;
+ this.links = links;
+ this.rules = inline.normal;
+ this.renderer = this.options.renderer || new Renderer();
+ this.renderer.options = this.options;
+
+ if (!this.links) {
+ throw new Error('Tokens array requires a `links` property.');
+ }
+
+ if (this.options.pedantic) {
+ this.rules = inline.pedantic;
+ } else if (this.options.gfm) {
+ if (this.options.breaks) {
+ this.rules = inline.breaks;
+ } else {
+ this.rules = inline.gfm;
+ }
+ }
+}
+
+/**
+ * Expose Inline Rules
+ */
+
+InlineLexer.rules = inline;
+
+/**
+ * Static Lexing/Compiling Method
+ */
+
+InlineLexer.output = function(src, links, options) {
+ var inline = new InlineLexer(links, options);
+ return inline.output(src);
+};
+
+/**
+ * Lexing/Compiling
+ */
+
+InlineLexer.prototype.output = function(src) {
+ var out = '',
+ link,
+ text,
+ href,
+ title,
+ cap;
+
+ while (src) {
+ // escape
+ if (cap = this.rules.escape.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += cap[1];
+ continue;
+ }
+
+ // autolink
+ if (cap = this.rules.autolink.exec(src)) {
+ src = src.substring(cap[0].length);
+ if (cap[2] === '@') {
+ text = escape(this.mangle(cap[1]));
+ href = 'mailto:' + text;
+ } else {
+ text = escape(cap[1]);
+ href = text;
+ }
+ out += this.renderer.link(href, null, text);
+ continue;
+ }
+
+ // url (gfm)
+ if (!this.inLink && (cap = this.rules.url.exec(src))) {
+ cap[0] = this.rules._backpedal.exec(cap[0])[0];
+ src = src.substring(cap[0].length);
+ if (cap[2] === '@') {
+ text = escape(cap[0]);
+ href = 'mailto:' + text;
+ } else {
+ text = escape(cap[0]);
+ if (cap[1] === 'www.') {
+ href = 'http://' + text;
+ } else {
+ href = text;
+ }
+ }
+ out += this.renderer.link(href, null, text);
+ continue;
+ }
+
+ // tag
+ if (cap = this.rules.tag.exec(src)) {
+ if (!this.inLink && /^<a /i.test(cap[0])) {
+ this.inLink = true;
+ } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
+ this.inLink = false;
+ }
+ src = src.substring(cap[0].length);
+ out += this.options.sanitize
+ ? this.options.sanitizer
+ ? this.options.sanitizer(cap[0])
+ : escape(cap[0])
+ : cap[0]
+ continue;
+ }
+
+ // link
+ if (cap = this.rules.link.exec(src)) {
+ src = src.substring(cap[0].length);
+ this.inLink = true;
+ href = cap[2];
+ if (this.options.pedantic) {
+ link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
+
+ if (link) {
+ href = link[1];
+ title = link[3];
+ } else {
+ title = '';
+ }
+ } else {
+ title = cap[3] ? cap[3].slice(1, -1) : '';
+ }
+ href = href.trim().replace(/^<([\s\S]*)>$/, '$1');
+ out += this.outputLink(cap, {
+ href: InlineLexer.escapes(href),
+ title: InlineLexer.escapes(title)
+ });
+ this.inLink = false;
+ continue;
+ }
+
+ // reflink, nolink
+ if ((cap = this.rules.reflink.exec(src))
+ || (cap = this.rules.nolink.exec(src))) {
+ src = src.substring(cap[0].length);
+ link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
+ link = this.links[link.toLowerCase()];
+ if (!link || !link.href) {
+ out += cap[0].charAt(0);
+ src = cap[0].substring(1) + src;
+ continue;
+ }
+ this.inLink = true;
+ out += this.outputLink(cap, link);
+ this.inLink = false;
+ continue;
+ }
+
+ // strong
+ if (cap = this.rules.strong.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1]));
+ continue;
+ }
+
+ // em
+ if (cap = this.rules.em.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1]));
+ continue;
+ }
+
+ // code
+ if (cap = this.rules.code.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.codespan(escape(cap[2].trim(), true));
+ continue;
+ }
+
+ // br
+ if (cap = this.rules.br.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.br();
+ continue;
+ }
+
+ // del (gfm)
+ if (cap = this.rules.del.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.del(this.output(cap[1]));
+ continue;
+ }
+
+ // text
+ if (cap = this.rules.text.exec(src)) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.text(escape(this.smartypants(cap[0])));
+ continue;
+ }
+
+ if (src) {
+ throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
+ }
+ }
+
+ return out;
+};
+
+InlineLexer.escapes = function(text) {
+ return text ? text.replace(InlineLexer.rules._escapes, '$1') : text;
+}
+
+/**
+ * Compile Link
+ */
+
+InlineLexer.prototype.outputLink = function(cap, link) {
+ var href = link.href,
+ title = link.title ? escape(link.title) : null;
+
+ return cap[0].charAt(0) !== '!'
+ ? this.renderer.link(href, title, this.output(cap[1]))
+ : this.renderer.image(href, title, escape(cap[1]));
+};
+
+/**
+ * Smartypants Transformations
+ */
+
+InlineLexer.prototype.smartypants = function(text) {
+ if (!this.options.smartypants) return text;
+ return text
+ // em-dashes
+ .replace(/---/g, '\u2014')
+ // en-dashes
+ .replace(/--/g, '\u2013')
+ // opening singles
+ .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
+ // closing singles & apostrophes
+ .replace(/'/g, '\u2019')
+ // opening doubles
+ .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
+ // closing doubles
+ .replace(/"/g, '\u201d')
+ // ellipses
+ .replace(/\.{3}/g, '\u2026');
+};
+
+/**
+ * Mangle Links
+ */
+
+InlineLexer.prototype.mangle = function(text) {
+ if (!this.options.mangle) return text;
+ var out = '',
+ l = text.length,
+ i = 0,
+ ch;
+
+ for (; i < l; i++) {
+ ch = text.charCodeAt(i);
+ if (Math.random() > 0.5) {
+ ch = 'x' + ch.toString(16);
+ }
+ out += '&#' + ch + ';';
+ }
+
+ return out;
+};
+
+/**
+ * Renderer
+ */
+
+function Renderer(options) {
+ this.options = options || marked.defaults;
+}
+
+Renderer.prototype.code = function(code, lang, escaped) {
+ if (this.options.highlight) {
+ var out = this.options.highlight(code, lang);
+ if (out != null && out !== code) {
+ escaped = true;
+ code = out;
+ }
+ }
+
+ if (!lang) {
+ return '<pre><code>'
+ + (escaped ? code : escape(code, true))
+ + '</code></pre>';
+ }
+
+ return '<pre><code class="'
+ + this.options.langPrefix
+ + escape(lang, true)
+ + '">'
+ + (escaped ? code : escape(code, true))
+ + '</code></pre>\n';
+};
+
+Renderer.prototype.blockquote = function(quote) {
+ return '<blockquote>\n' + quote + '</blockquote>\n';
+};
+
+Renderer.prototype.html = function(html) {
+ return html;
+};
+
+Renderer.prototype.heading = function(text, level, raw) {
+ if (this.options.headerIds) {
+ return '<h'
+ + level
+ + ' id="'
+ + this.options.headerPrefix
+ + raw.toLowerCase().replace(/[^\w]+/g, '-')
+ + '">'
+ + text
+ + '</h'
+ + level
+ + '>\n';
+ }
+ // ignore IDs
+ return '<h' + level + '>' + text + '</h' + level + '>\n';
+};
+
+Renderer.prototype.hr = function() {
+ return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
+};
+
+Renderer.prototype.list = function(body, ordered, start) {
+ var type = ordered ? 'ol' : 'ul',
+ startatt = (ordered && start !== 1) ? (' start="' + start + '"') : '';
+ return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
+};
+
+Renderer.prototype.listitem = function(text) {
+ return '<li>' + text + '</li>\n';
+};
+
+Renderer.prototype.checkbox = function(checked) {
+ return '<input '
+ + (checked ? 'checked="" ' : '')
+ + 'disabled="" type="checkbox"'
+ + (this.options.xhtml ? ' /' : '')
+ + '> ';
+}
+
+Renderer.prototype.paragraph = function(text) {
+ return '<p>' + text + '</p>\n';
+};
+
+Renderer.prototype.table = function(header, body) {
+ if (body) body = '<tbody>' + body + '</tbody>';
+
+ return '<table>\n'
+ + '<thead>\n'
+ + header
+ + '</thead>\n'
+ + body
+ + '</table>\n';
+};
+
+Renderer.prototype.tablerow = function(content) {
+ return '<tr>\n' + content + '</tr>\n';
+};
+
+Renderer.prototype.tablecell = function(content, flags) {
+ var type = flags.header ? 'th' : 'td';
+ var tag = flags.align
+ ? '<' + type + ' align="' + flags.align + '">'
+ : '<' + type + '>';
+ return tag + content + '</' + type + '>\n';
+};
+
+// span level renderer
+Renderer.prototype.strong = function(text) {
+ return '<strong>' + text + '</strong>';
+};
+
+Renderer.prototype.em = function(text) {
+ return '<em>' + text + '</em>';
+};
+
+Renderer.prototype.codespan = function(text) {
+ return '<code>' + text + '</code>';
+};
+
+Renderer.prototype.br = function() {
+ return this.options.xhtml ? '<br/>' : '<br>';
+};
+
+Renderer.prototype.del = function(text) {
+ return '<del>' + text + '</del>';
+};
+
+Renderer.prototype.link = function(href, title, text) {
+ if (this.options.sanitize) {
+ try {
+ var prot = decodeURIComponent(unescape(href))
+ .replace(/[^\w:]/g, '')
+ .toLowerCase();
+ } catch (e) {
+ return text;
+ }
+ if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
+ return text;
+ }
+ }
+ if (this.options.baseUrl && !originIndependentUrl.test(href)) {
+ href = resolveUrl(this.options.baseUrl, href);
+ }
+ try {
+ href = encodeURI(href).replace(/%25/g, '%');
+ } catch (e) {
+ return text;
+ }
+ var out = '<a href="' + escape(href) + '"';
+ if (title) {
+ out += ' title="' + title + '"';
+ }
+ out += '>' + text + '</a>';
+ return out;
+};
+
+Renderer.prototype.image = function(href, title, text) {
+ if (this.options.baseUrl && !originIndependentUrl.test(href)) {
+ href = resolveUrl(this.options.baseUrl, href);
+ }
+ var out = '<img src="' + href + '" alt="' + text + '"';
+ if (title) {
+ out += ' title="' + title + '"';
+ }
+ out += this.options.xhtml ? '/>' : '>';
+ return out;
+};
+
+Renderer.prototype.text = function(text) {
+ return text;
+};
+
+/**
+ * TextRenderer
+ * returns only the textual part of the token
+ */
+
+function TextRenderer() {}
+
+// no need for block level renderers
+
+TextRenderer.prototype.strong =
+TextRenderer.prototype.em =
+TextRenderer.prototype.codespan =
+TextRenderer.prototype.del =
+TextRenderer.prototype.text = function (text) {
+ return text;
+}
+
+TextRenderer.prototype.link =
+TextRenderer.prototype.image = function(href, title, text) {
+ return '' + text;
+}
+
+TextRenderer.prototype.br = function() {
+ return '';
+}
+
+/**
+ * Parsing & Compiling
+ */
+
+function Parser(options) {
+ this.tokens = [];
+ this.token = null;
+ this.options = options || marked.defaults;
+ this.options.renderer = this.options.renderer || new Renderer();
+ this.renderer = this.options.renderer;
+ this.renderer.options = this.options;
+}
+
+/**
+ * Static Parse Method
+ */
+
+Parser.parse = function(src, options) {
+ var parser = new Parser(options);
+ return parser.parse(src);
+};
+
+/**
+ * Parse Loop
+ */
+
+Parser.prototype.parse = function(src) {
+ this.inline = new InlineLexer(src.links, this.options);
+ // use an InlineLexer with a TextRenderer to extract pure text
+ this.inlineText = new InlineLexer(
+ src.links,
+ merge({}, this.options, {renderer: new TextRenderer()})
+ );
+ this.tokens = src.reverse();
+
+ var out = '';
+ while (this.next()) {
+ out += this.tok();
+ }
+
+ return out;
+};
+
+/**
+ * Next Token
+ */
+
+Parser.prototype.next = function() {
+ return this.token = this.tokens.pop();
+};
+
+/**
+ * Preview Next Token
+ */
+
+Parser.prototype.peek = function() {
+ return this.tokens[this.tokens.length - 1] || 0;
+};
+
+/**
+ * Parse Text Tokens
+ */
+
+Parser.prototype.parseText = function() {
+ var body = this.token.text;
+
+ while (this.peek().type === 'text') {
+ body += '\n' + this.next().text;
+ }
+
+ return this.inline.output(body);
+};
+
+/**
+ * Parse Current Token
+ */
+
+Parser.prototype.tok = function() {
+ switch (this.token.type) {
+ case 'space': {
+ return '';
+ }
+ case 'hr': {
+ return this.renderer.hr();
+ }
+ case 'heading': {
+ return this.renderer.heading(
+ this.inline.output(this.token.text),
+ this.token.depth,
+ unescape(this.inlineText.output(this.token.text)));
+ }
+ case 'code': {
+ return this.renderer.code(this.token.text,
+ this.token.lang,
+ this.token.escaped);
+ }
+ case 'table': {
+ var header = '',
+ body = '',
+ i,
+ row,
+ cell,
+ j;
+
+ // header
+ cell = '';
+ for (i = 0; i < this.token.header.length; i++) {
+ cell += this.renderer.tablecell(
+ this.inline.output(this.token.header[i]),
+ { header: true, align: this.token.align[i] }
+ );
+ }
+ header += this.renderer.tablerow(cell);
+
+ for (i = 0; i < this.token.cells.length; i++) {
+ row = this.token.cells[i];
+
+ cell = '';
+ for (j = 0; j < row.length; j++) {
+ cell += this.renderer.tablecell(
+ this.inline.output(row[j]),
+ { header: false, align: this.token.align[j] }
+ );
+ }
+
+ body += this.renderer.tablerow(cell);
+ }
+ return this.renderer.table(header, body);
+ }
+ case 'blockquote_start': {
+ body = '';
+
+ while (this.next().type !== 'blockquote_end') {
+ body += this.tok();
+ }
+
+ return this.renderer.blockquote(body);
+ }
+ case 'list_start': {
+ body = '';
+ var ordered = this.token.ordered,
+ start = this.token.start;
+
+ while (this.next().type !== 'list_end') {
+ body += this.tok();
+ }
+
+ return this.renderer.list(body, ordered, start);
+ }
+ case 'list_item_start': {
+ body = '';
+
+ if (this.token.task) {
+ body += this.renderer.checkbox(this.token.checked);
+ }
+
+ while (this.next().type !== 'list_item_end') {
+ body += this.token.type === 'text'
+ ? this.parseText()
+ : this.tok();
+ }
+
+ return this.renderer.listitem(body);
+ }
+ case 'loose_item_start': {
+ body = '';
+
+ while (this.next().type !== 'list_item_end') {
+ body += this.tok();
+ }
+
+ return this.renderer.listitem(body);
+ }
+ case 'html': {
+ // TODO parse inline content if parameter markdown=1
+ return this.renderer.html(this.token.text);
+ }
+ case 'paragraph': {
+ return this.renderer.paragraph(this.inline.output(this.token.text));
+ }
+ case 'text': {
+ return this.renderer.paragraph(this.parseText());
+ }
+ }
+};
+
+/**
+ * Helpers
+ */
+
+function escape(html, encode) {
+ return html
+ .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;');
+}
+
+function unescape(html) {
+ // explicitly match decimal, hex, and named HTML entities
+ return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) {
+ n = n.toLowerCase();
+ if (n === 'colon') return ':';
+ if (n.charAt(0) === '#') {
+ return n.charAt(1) === 'x'
+ ? String.fromCharCode(parseInt(n.substring(2), 16))
+ : String.fromCharCode(+n.substring(1));
+ }
+ return '';
+ });
+}
+
+function edit(regex, opt) {
+ regex = regex.source || regex;
+ opt = opt || '';
+ return {
+ replace: function(name, val) {
+ val = val.source || val;
+ val = val.replace(/(^|[^\[])\^/g, '$1');
+ regex = regex.replace(name, val);
+ return this;
+ },
+ getRegex: function() {
+ return new RegExp(regex, opt);
+ }
+ };
+}
+
+function resolveUrl(base, href) {
+ if (!baseUrls[' ' + base]) {
+ // we can ignore everything in base after the last slash of its path component,
+ // but we might need to add _that_
+ // https://tools.ietf.org/html/rfc3986#section-3
+ if (/^[^:]+:\/*[^/]*$/.test(base)) {
+ baseUrls[' ' + base] = base + '/';
+ } else {
+ baseUrls[' ' + base] = base.replace(/[^/]*$/, '');
+ }
+ }
+ base = baseUrls[' ' + base];
+
+ if (href.slice(0, 2) === '//') {
+ return base.replace(/:[\s\S]*/, ':') + href;
+ } else if (href.charAt(0) === '/') {
+ return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href;
+ } else {
+ return base + href;
+ }
+}
+var baseUrls = {};
+var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
+
+function noop() {}
+noop.exec = noop;
+
+function merge(obj) {
+ var i = 1,
+ target,
+ key;
+
+ for (; i < arguments.length; i++) {
+ target = arguments[i];
+ for (key in target) {
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
+ obj[key] = target[key];
+ }
+ }
+ }
+
+ return obj;
+}
+
+function splitCells(tableRow, count) {
+ var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */),
+ i = 0;
+
+ if (cells.length > count) {
+ cells.splice(count);
+ } else {
+ while (cells.length < count) cells.push('');
+ }
+
+ for (; i < cells.length; i++) {
+ cells[i] = cells[i].replace(/\\\|/g, '|');
+ }
+ return cells;
+}
+
+/**
+ * Marked
+ */
+
+function marked(src, opt, callback) {
+ // throw error in case of non string input
+ if (typeof src === 'undefined' || src === null) {
+ throw new Error('marked(): input parameter is undefined or null');
+ }
+ if (typeof src !== 'string') {
+ throw new Error('marked(): input parameter is of type '
+ + Object.prototype.toString.call(src) + ', string expected');
+ }
+
+ if (callback || typeof opt === 'function') {
+ if (!callback) {
+ callback = opt;
+ opt = null;
+ }
+
+ opt = merge({}, marked.defaults, opt || {});
+
+ var highlight = opt.highlight,
+ tokens,
+ pending,
+ i = 0;
+
+ try {
+ tokens = Lexer.lex(src, opt)
+ } catch (e) {
+ return callback(e);
+ }
+
+ pending = tokens.length;
+
+ var done = function(err) {
+ if (err) {
+ opt.highlight = highlight;
+ return callback(err);
+ }
+
+ var out;
+
+ try {
+ out = Parser.parse(tokens, opt);
+ } catch (e) {
+ err = e;
+ }
+
+ opt.highlight = highlight;
+
+ return err
+ ? callback(err)
+ : callback(null, out);
+ };
+
+ if (!highlight || highlight.length < 3) {
+ return done();
+ }
+
+ delete opt.highlight;
+
+ if (!pending) return done();
+
+ for (; i < tokens.length; i++) {
+ (function(token) {
+ if (token.type !== 'code') {
+ return --pending || done();
+ }
+ return highlight(token.text, token.lang, function(err, code) {
+ if (err) return done(err);
+ if (code == null || code === token.text) {
+ return --pending || done();
+ }
+ token.text = code;
+ token.escaped = true;
+ --pending || done();
+ });
+ })(tokens[i]);
+ }
+
+ return;
+ }
+ try {
+ if (opt) opt = merge({}, marked.defaults, opt);
+ return Parser.parse(Lexer.lex(src, opt), opt);
+ } catch (e) {
+ e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+ if ((opt || marked.defaults).silent) {
+ return '<p>An error occurred:</p><pre>'
+ + escape(e.message + '', true)
+ + '</pre>';
+ }
+ throw e;
+ }
+}
+
+/**
+ * Options
+ */
+
+marked.options =
+marked.setOptions = function(opt) {
+ merge(marked.defaults, opt);
+ return marked;
+};
+
+marked.getDefaults = function () {
+ return {
+ baseUrl: null,
+ breaks: false,
+ gfm: true,
+ headerIds: true,
+ headerPrefix: '',
+ highlight: null,
+ langPrefix: 'language-',
+ mangle: true,
+ pedantic: false,
+ renderer: new Renderer(),
+ sanitize: false,
+ sanitizer: null,
+ silent: false,
+ smartLists: false,
+ smartypants: false,
+ tables: true,
+ xhtml: false
+ };
+}
+
+marked.defaults = marked.getDefaults();
+
+/**
+ * Expose
+ */
+
+marked.Parser = Parser;
+marked.parser = Parser.parse;
+
+marked.Renderer = Renderer;
+marked.TextRenderer = TextRenderer;
+
+marked.Lexer = Lexer;
+marked.lexer = Lexer.lex;
+
+marked.InlineLexer = InlineLexer;
+marked.inlineLexer = InlineLexer.output;
+
+marked.parse = marked;
+
+if (typeof module !== 'undefined' && typeof exports === 'object') {
+ module.exports = marked;
+} else if (typeof define === 'function' && define.amd) {
+ define(function() { return marked; });
+} else {
+ root.marked = marked;
+}
+})(this || (typeof window !== 'undefined' ? window : global));
diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json b/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json
new file mode 100644
index 000000000..de5458eff
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json
@@ -0,0 +1,35 @@
+[
+ {
+ "Id": "markdowneditor-marked",
+ "Name": "Marked (WebEngine Markdown Editor example)",
+ "QDocModule": "qtwebengine",
+ "QtUsage": "Marked is used in the WebEngine MarkDown Editor example",
+ "QtParts": [ "examples" ],
+ "Files": "marked.js",
+ "Description": "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.",
+ "Homepage": "https://github.com/chjj/marked",
+ "Version": "0.4.0",
+ "DownloadLocation": "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js",
+ "Copyright": "Copyright (c) 2011-2018, Christopher Jeffrey",
+ "License": "MIT License",
+ "LicenseId": "MIT",
+ "LicenseFile": "MARKED-LICENSE.txt"
+ },
+ {
+ "Id": "markdowneditor-markdowncss",
+ "Name": "Markdown.css (WebEngine Markdown Editor example)",
+ "QDocModule": "qtwebengine",
+ "QtUsage": "markdown.css is used in the WebEngine MarkDown Editor example",
+ "QtParts": [ "examples" ],
+ "Files": "markdown.css",
+ "Description": "Markdown.css is better default styling for your Markdown files.",
+ "Homepage": "https://kevinburke.bitbucket.io/markdowncss/",
+ "Version": "188530e4b5d020d7e237fc6b26be13ebf4a8def3",
+ "DownloadLocation": "https://bitbucket.org/kevinburke/markdowncss/src/188530e4b5d020d7e237fc6b26be13ebf4a8def3/markdown.css",
+ "Copyright": "Copyright 2011 Kevin Burke
+ Copyright Twitter Inc.",
+ "License": "Apache License 2.0",
+ "LicenseId": "Apache-2.0",
+ "LicenseFile": "MARKDOWN-LICENSE.txt"
+ }
+]
diff --git a/examples/webenginewidgets/markdowneditor/resources/default.md b/examples/webenginewidgets/markdowneditor/resources/default.md
new file mode 100644
index 000000000..d29cdfe60
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/default.md
@@ -0,0 +1,12 @@
+## WebEngine Markdown Editor Example
+
+This example uses [QWebEngineView](https://doc.qt.io/qt-5/qwebengineview.html)
+to preview text written using the [Markdown](https://en.wikipedia.org/wiki/Markdown)
+syntax.
+
+### Acknowledgments
+
+The conversion from Markdown to HTML is done with the help of the
+[marked JavaScript library](https://github.com/chjj/marked) by _Christopher Jeffrey_.
+The [style sheet](https://kevinburke.bitbucket.io/markdowncss/)
+was created by _Kevin Burke_.
diff --git a/examples/webenginewidgets/markdowneditor/resources/index.html b/examples/webenginewidgets/markdowneditor/resources/index.html
new file mode 100644
index 000000000..c8e30b49b
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/index.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang="en">
+<meta charset="utf-8">
+<head>
+ <link rel="stylesheet" type="text/css" href="3rdparty/markdown.css">
+ <script src="3rdparty/marked.js"></script>
+ <script src="qrc:/qtwebchannel/qwebchannel.js"></script>
+</head>
+<body>
+ <div id="placeholder"></div>
+ <script>
+ 'use strict';
+
+ var placeholder = document.getElementById('placeholder');
+
+ var updateText = function(text) {
+ placeholder.innerHTML = marked.parse(text);
+ }
+
+ new QWebChannel(qt.webChannelTransport,
+ function(channel) {
+ var content = channel.objects.content;
+ updateText(content.text);
+ content.textChanged.connect(updateText);
+ }
+ );
+ </script>
+</body>
+</html>
+
+
+
diff --git a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc
new file mode 100644
index 000000000..bc738f1cf
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc
@@ -0,0 +1,8 @@
+<RCC>
+ <qresource prefix="/">
+ <file>default.md</file>
+ <file>index.html</file>
+ <file>3rdparty/markdown.css</file>
+ <file>3rdparty/marked.js</file>
+ </qresource>
+</RCC>
diff --git a/examples/webenginewidgets/markdowneditor/ui_mainwindow.py b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py
new file mode 100644
index 000000000..0be769119
--- /dev/null
+++ b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'mainwindow.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
+ QCursor, QFont, QFontDatabase, QGradient,
+ QIcon, QImage, QKeySequence, QLinearGradient,
+ QPainter, QPalette, QPixmap, QRadialGradient,
+ QTransform)
+from PySide6.QtWebEngineWidgets import QWebEngineView
+from PySide6.QtWidgets import (QApplication, QHBoxLayout, QMainWindow, QMenu,
+ QMenuBar, QPlainTextEdit, QSizePolicy, QSplitter,
+ QStatusBar, QWidget)
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ if not MainWindow.objectName():
+ MainWindow.setObjectName(u"MainWindow")
+ MainWindow.resize(800, 600)
+ self.actionOpen = QAction(MainWindow)
+ self.actionOpen.setObjectName(u"actionOpen")
+ self.actionSave = QAction(MainWindow)
+ self.actionSave.setObjectName(u"actionSave")
+ self.actionExit = QAction(MainWindow)
+ self.actionExit.setObjectName(u"actionExit")
+ self.actionSaveAs = QAction(MainWindow)
+ self.actionSaveAs.setObjectName(u"actionSaveAs")
+ self.actionNew = QAction(MainWindow)
+ self.actionNew.setObjectName(u"actionNew")
+ self.centralwidget = QWidget(MainWindow)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.horizontalLayout = QHBoxLayout(self.centralwidget)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.splitter = QSplitter(self.centralwidget)
+ self.splitter.setObjectName(u"splitter")
+ self.splitter.setOrientation(Qt.Horizontal)
+ self.editor = QPlainTextEdit(self.splitter)
+ self.editor.setObjectName(u"editor")
+ self.splitter.addWidget(self.editor)
+ self.preview = QWebEngineView(self.splitter)
+ self.preview.setObjectName(u"preview")
+ self.splitter.addWidget(self.preview)
+
+ self.horizontalLayout.addWidget(self.splitter)
+
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.menubar = QMenuBar(MainWindow)
+ self.menubar.setObjectName(u"menubar")
+ self.menubar.setGeometry(QRect(0, 0, 800, 26))
+ self.menu_File = QMenu(self.menubar)
+ self.menu_File.setObjectName(u"menu_File")
+ MainWindow.setMenuBar(self.menubar)
+ self.statusbar = QStatusBar(MainWindow)
+ self.statusbar.setObjectName(u"statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+
+ self.menubar.addAction(self.menu_File.menuAction())
+ self.menu_File.addAction(self.actionNew)
+ self.menu_File.addAction(self.actionOpen)
+ self.menu_File.addAction(self.actionSave)
+ self.menu_File.addAction(self.actionSaveAs)
+ self.menu_File.addSeparator()
+ self.menu_File.addAction(self.actionExit)
+
+ self.retranslateUi(MainWindow)
+
+ QMetaObject.connectSlotsByName(MainWindow)
+ # setupUi
+
+ def retranslateUi(self, MainWindow):
+ MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MarkDown Editor", None))
+ self.actionOpen.setText(QCoreApplication.translate("MainWindow", u"&Open...", None))
+#if QT_CONFIG(tooltip)
+ self.actionOpen.setToolTip(QCoreApplication.translate("MainWindow", u"Open document", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionOpen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionSave.setText(QCoreApplication.translate("MainWindow", u"&Save", None))
+#if QT_CONFIG(tooltip)
+ self.actionSave.setToolTip(QCoreApplication.translate("MainWindow", u"Save current document", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionSave.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+S", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionExit.setText(QCoreApplication.translate("MainWindow", u"E&xit", None))
+#if QT_CONFIG(tooltip)
+ self.actionExit.setToolTip(QCoreApplication.translate("MainWindow", u"Exit editor", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionExit.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Q", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionSaveAs.setText(QCoreApplication.translate("MainWindow", u"Save &As...", None))
+#if QT_CONFIG(tooltip)
+ self.actionSaveAs.setToolTip(QCoreApplication.translate("MainWindow", u"Save document under different name", None))
+#endif // QT_CONFIG(tooltip)
+ self.actionNew.setText(QCoreApplication.translate("MainWindow", u"&New", None))
+#if QT_CONFIG(tooltip)
+ self.actionNew.setToolTip(QCoreApplication.translate("MainWindow", u"Create new document", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionNew.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+N", None))
+#endif // QT_CONFIG(shortcut)
+ self.menu_File.setTitle(QCoreApplication.translate("MainWindow", u"&File", None))
+ # retranslateUi
+
diff --git a/examples/webenginewidgets/notifications/doc/notifications.png b/examples/webenginewidgets/notifications/doc/notifications.png
new file mode 100644
index 000000000..3540be8d1
--- /dev/null
+++ b/examples/webenginewidgets/notifications/doc/notifications.png
Binary files differ
diff --git a/examples/webenginewidgets/notifications/doc/notifications.rst b/examples/webenginewidgets/notifications/doc/notifications.rst
new file mode 100644
index 000000000..a06ebfbc5
--- /dev/null
+++ b/examples/webenginewidgets/notifications/doc/notifications.rst
@@ -0,0 +1,8 @@
+WebEngine Notifications Example
+===============================
+
+Python port of C++ `WebEngine Notifications <https://doc.qt.io/qt-6/qtwebengine-webenginewidgets-notifications-example.html>`_
+
+.. image:: notifications.png
+ :width: 400
+ :alt: Notifications Example Screenshot
diff --git a/examples/webenginewidgets/notifications/main.py b/examples/webenginewidgets/notifications/main.py
new file mode 100644
index 000000000..b59aead97
--- /dev/null
+++ b/examples/webenginewidgets/notifications/main.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 WebEngineWidgets Notifications Example"""
+
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import QUrl, QCoreApplication
+from PySide6.QtWidgets import QApplication
+from PySide6.QtWebEngineCore import QWebEnginePage
+from PySide6.QtWebEngineWidgets import QWebEngineView
+from PySide6.QtGui import QDesktopServices
+
+from notificationpopup import NotificationPopup
+
+
+class WebEnginePage(QWebEnginePage):
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ def acceptNavigationRequest(self, url: QUrl, *_):
+ if url.scheme != "https":
+ return True
+ QDesktopServices.openUrl(url)
+ return False
+
+
+if __name__ == '__main__':
+
+ src_dir = Path(__file__).resolve().parent
+ QCoreApplication.setOrganizationName("QtProject")
+ app = QApplication(sys.argv)
+ view = QWebEngineView()
+
+ # set custom page to open all page's links for https scheme in system browser
+ view.setPage(WebEnginePage(view))
+
+ def set_feature_permission(origin: QUrl, feature: QWebEnginePage.Feature):
+ if feature != QWebEnginePage.Notifications:
+ return
+
+ view.page().setFeaturePermission(origin, feature, QWebEnginePage.PermissionGrantedByUser)
+
+ view.page().featurePermissionRequested.connect(set_feature_permission)
+ profile = view.page().profile()
+ popup = NotificationPopup(view)
+
+ def presentNotification(notification):
+ popup.present(notification)
+
+ profile.setNotificationPresenter(presentNotification)
+ view.resize(640, 480)
+ view.show()
+ view.setUrl(QUrl.fromLocalFile(src_dir / "resources" / "index.html"))
+
+ sys.exit(app.exec())
diff --git a/examples/webenginewidgets/notifications/notificationpopup.py b/examples/webenginewidgets/notifications/notificationpopup.py
new file mode 100644
index 000000000..e68ce3d6f
--- /dev/null
+++ b/examples/webenginewidgets/notifications/notificationpopup.py
@@ -0,0 +1,68 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Qt, QTimer, QPoint, Slot
+from PySide6.QtWidgets import (QWidget, QHBoxLayout, QLabel, QVBoxLayout, QSpacerItem, QSizePolicy,
+ QPushButton)
+from PySide6.QtWebEngineCore import QWebEngineNotification
+from PySide6.QtGui import QPixmap, QMouseEvent
+
+
+class NotificationPopup(QWidget):
+ def __init__(self, parent) -> None:
+ super().__init__(parent)
+ self.notification = None
+ self.m_icon, self.m_title, self.m_message = QLabel(), QLabel(), QLabel()
+ self.setWindowFlags(Qt.ToolTip)
+
+ rootLayout = QHBoxLayout(self)
+ rootLayout.addWidget(self.m_icon)
+
+ bodyLayout = QVBoxLayout()
+ rootLayout.addLayout(bodyLayout)
+
+ titleLayout = QHBoxLayout()
+ bodyLayout.addLayout(titleLayout)
+
+ titleLayout.addWidget(self.m_title)
+ titleLayout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding))
+
+ close = QPushButton("Close")
+ titleLayout.addWidget(close)
+ close.clicked.connect(self.onClosed)
+
+ bodyLayout.addWidget(self.m_message)
+ self.adjustSize()
+
+ def present(self, newNotification: QWebEngineNotification):
+ if self.notification:
+ self.notification.close()
+
+ self.notification = newNotification
+
+ self.m_title.setText("<b>" + self.notification.title() + "</b>")
+ self.m_message.setText(self.notification.message())
+ self.m_icon.setPixmap(QPixmap.fromImage(self.notification.icon())
+ .scaledToHeight(self.m_icon.height()))
+
+ self.show()
+ self.notification.show()
+
+ self.notification.closed.connect(self.onClosed)
+ QTimer.singleShot(10000, lambda: self.onClosed())
+
+ self.move(self.parentWidget().mapToGlobal(self.parentWidget().rect().bottomRight()
+ - QPoint(self.width() + 10, self.height() + 10)))
+
+ @Slot()
+ def onClosed(self):
+ self.hide()
+ if self.notification:
+ self.notification.close()
+ self.notification = None
+
+ def mouseReleaseEvent(self, event: QMouseEvent) -> None:
+ QWidget.mouseReleaseEvent(event)
+ if self.notification and event.button() == Qt.LeftButton:
+ self.notification.click()
+ self.onClosed()
diff --git a/examples/webenginewidgets/notifications/notifications.pyproject b/examples/webenginewidgets/notifications/notifications.pyproject
new file mode 100644
index 000000000..0a3d3c4c5
--- /dev/null
+++ b/examples/webenginewidgets/notifications/notifications.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "notificationpopup.py"]
+}
diff --git a/examples/webenginewidgets/notifications/resources/icon.png b/examples/webenginewidgets/notifications/resources/icon.png
new file mode 100644
index 000000000..4c3870c06
--- /dev/null
+++ b/examples/webenginewidgets/notifications/resources/icon.png
Binary files differ
diff --git a/examples/webenginewidgets/notifications/resources/index.html b/examples/webenginewidgets/notifications/resources/index.html
new file mode 100644
index 000000000..99dbac683
--- /dev/null
+++ b/examples/webenginewidgets/notifications/resources/index.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<html>
+<head>
+<title>Web Notifications Example</title>
+<script>
+ var notificationsCreated = 0
+
+ function getPermission() { return document.Notification }
+ function resetPermission(permission = 'default') {
+ document.Notification = permission
+ document.getElementById('state').value = getPermission()
+ }
+
+ function createNotification() {
+ let title = 'Notification #' + ++notificationsCreated
+ let options = { body: 'Visit doc.qt.io for more info!', icon: 'icon.png', }
+
+ let notification = new Notification(title, options)
+ document.notification = notification
+
+ notification.onerror = function(error) {
+ document.getElementById('act').value += ' with error'
+ document.notification = null
+ }
+ notification.onshow = function() {
+ document.getElementById('act').value += ', shown'
+ document.getElementById('close').style.display = 'inline'
+ }
+ notification.onclick = function() {
+ document.getElementById('act').value += ', clicked'
+ }
+ notification.onclose = function() {
+ if (document.notification && notification == document.notification) {
+ document.getElementById('act').value += ' and closed'
+ document.getElementById('close').style.display = 'none'
+ document.notification = null
+ }
+ }
+
+ console.log('...notification created [Title: ' + title + ']')
+ document.getElementById('act').value = 'Notification was created'
+ }
+
+ function onMakeNotification() {
+ if (getPermission() == 'granted') {
+ createNotification()
+ } else if (getPermission() == 'denied') {
+ setTimeout(function() {
+ if (window.confirm('Notifications are disabled!\n' +
+ 'Permission needs to be granted by user. Reset?'))
+ resetPermission()
+ }, 1)
+ } else {
+ Notification.requestPermission().then(function (permission) {
+ console.info('notifications request: ' + permission)
+ resetPermission(permission)
+ if (permission == 'granted')
+ createNotification()
+ })
+ }
+ }
+
+ function closeNotification() { if (document.notification) document.notification.close() }
+
+ document.addEventListener('DOMContentLoaded', function() {
+ resetPermission(Notification.permission) })
+</script>
+</head>
+<body style='text-align:center;'>
+ <h3>Click the button to send a notification</h3>
+
+ <button onclick='onMakeNotification()'>Notify!</button>
+
+ <p>
+ <output id='act'></output>
+ <button id='close' style='display: none;' onclick='closeNotification()'>Close</button>
+ </p><br>
+
+ <p>
+ <label for='state'>Permission:</label>
+ <output id='state'></output>
+ <button onclick='resetPermission()'>Reset</button>
+ </p><br>
+
+ <h4>More info can be found on:</h4>
+ <ul style='list-style-type: none;'>
+ <li>W3 <a href='https://www.w3.org/TR/notifications'>Web Notifications</a> standard</li>
+ <li>Documentation for <a href='https://doc.qt.io'>Qt WebEngine</a> module</li>
+ </ul>
+</body>
+</html>
diff --git a/examples/webenginewidgets/simplebrowser/browser.py b/examples/webenginewidgets/simplebrowser/browser.py
new file mode 100644
index 000000000..a124ea084
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/browser.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWebEngineCore import (qWebEngineChromiumVersion,
+ QWebEngineProfile, QWebEngineSettings)
+from PySide6.QtCore import QObject, Qt, Slot
+
+from downloadmanagerwidget import DownloadManagerWidget
+from browserwindow import BrowserWindow
+
+
+class Browser(QObject):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._windows = []
+ self._download_manager_widget = DownloadManagerWidget()
+ self._profile = None
+
+ # Quit application if the download manager window is the only
+ # remaining window
+ self._download_manager_widget.setAttribute(Qt.WA_QuitOnClose, False)
+
+ dp = QWebEngineProfile.defaultProfile()
+ dp.downloadRequested.connect(self._download_manager_widget.download_requested)
+
+ def create_hidden_window(self, offTheRecord=False):
+ if not offTheRecord and not self._profile:
+ name = "simplebrowser." + qWebEngineChromiumVersion()
+ self._profile = QWebEngineProfile(name)
+ s = self._profile.settings()
+ s.setAttribute(QWebEngineSettings.PluginsEnabled, True)
+ s.setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True)
+ s.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
+ s.setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls, False)
+ self._profile.downloadRequested.connect(
+ self._download_manager_widget.download_requested)
+
+ profile = QWebEngineProfile.defaultProfile() if offTheRecord else self._profile
+ main_window = BrowserWindow(self, profile, False)
+ self._windows.append(main_window)
+ main_window.about_to_close.connect(self._remove_window)
+ return main_window
+
+ def create_window(self, offTheRecord=False):
+ main_window = self.create_hidden_window(offTheRecord)
+ main_window.show()
+ return main_window
+
+ def create_dev_tools_window(self):
+ profile = (self._profile if self._profile
+ else QWebEngineProfile.defaultProfile())
+ main_window = BrowserWindow(self, profile, True)
+ self._windows.append(main_window)
+ main_window.about_to_close.connect(self._remove_window)
+ main_window.show()
+ return main_window
+
+ def windows(self):
+ return self._windows
+
+ def download_manager_widget(self):
+ return self._download_manager_widget
+
+ @Slot()
+ def _remove_window(self):
+ w = self.sender()
+ if w in self._windows:
+ del self._windows[self._windows.index(w)]
diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.py b/examples/webenginewidgets/simplebrowser/browserwindow.py
new file mode 100644
index 000000000..43b811200
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/browserwindow.py
@@ -0,0 +1,500 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtWebEngineCore import QWebEnginePage
+from PySide6.QtWidgets import (QMainWindow, QFileDialog,
+ QInputDialog, QLineEdit, QMenu, QMessageBox,
+ QProgressBar, QToolBar, QVBoxLayout, QWidget)
+from PySide6.QtGui import QAction, QGuiApplication, QIcon, QKeySequence
+from PySide6.QtCore import QUrl, Qt, Slot, Signal
+
+from tabwidget import TabWidget
+
+
+def remove_backspace(keys):
+ result = keys.copy()
+ # Chromium already handles navigate on backspace when appropriate.
+ for i, key in enumerate(result):
+ if (key[0].key() & Qt.Key_unknown) == Qt.Key_Backspace:
+ del result[i]
+ break
+ return result
+
+
+class BrowserWindow(QMainWindow):
+
+ about_to_close = Signal()
+
+ def __init__(self, browser, profile, forDevTools):
+ super().__init__()
+
+ self._progress_bar = None
+ self._history_back_action = None
+ self._history_forward_action = None
+ self._stop_action = None
+ self._reload_action = None
+ self._stop_reload_action = None
+ self._url_line_edit = None
+ self._fav_action = None
+ self._last_search = ""
+ self._toolbar = None
+
+ self._browser = browser
+ self._profile = profile
+ self._tab_widget = TabWidget(profile, self)
+
+ self._stop_icon = QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop,
+ QIcon(":process-stop.png"))
+ self._reload_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh,
+ QIcon(":view-refresh.png"))
+
+ self.setAttribute(Qt.WA_DeleteOnClose, True)
+ self.setFocusPolicy(Qt.ClickFocus)
+
+ if not forDevTools:
+ self._progress_bar = QProgressBar(self)
+
+ self._toolbar = self.create_tool_bar()
+ self.addToolBar(self._toolbar)
+ mb = self.menuBar()
+ mb.addMenu(self.create_file_menu(self._tab_widget))
+ mb.addMenu(self.create_edit_menu())
+ mb.addMenu(self.create_view_menu())
+ mb.addMenu(self.create_window_menu(self._tab_widget))
+ mb.addMenu(self.create_help_menu())
+
+ central_widget = QWidget(self)
+ layout = QVBoxLayout(central_widget)
+ layout.setSpacing(0)
+ layout.setContentsMargins(0, 0, 0, 0)
+ if not forDevTools:
+ self.addToolBarBreak()
+
+ self._progress_bar.setMaximumHeight(1)
+ self._progress_bar.setTextVisible(False)
+ s = "QProgressBar {border: 0px} QProgressBar.chunk {background-color: #da4453}"
+ self._progress_bar.setStyleSheet(s)
+
+ layout.addWidget(self._progress_bar)
+
+ layout.addWidget(self._tab_widget)
+ self.setCentralWidget(central_widget)
+
+ self._tab_widget.title_changed.connect(self.handle_web_view_title_changed)
+ if not forDevTools:
+ self._tab_widget.link_hovered.connect(self._show_status_message)
+ self._tab_widget.load_progress.connect(self.handle_web_view_load_progress)
+ self._tab_widget.web_action_enabled_changed.connect(
+ self.handle_web_action_enabled_changed)
+ self._tab_widget.url_changed.connect(self._url_changed)
+ self._tab_widget.fav_icon_changed.connect(self._fav_action.setIcon)
+ self._tab_widget.dev_tools_requested.connect(self.handle_dev_tools_requested)
+ self._url_line_edit.returnPressed.connect(self._address_return_pressed)
+ self._tab_widget.find_text_finished.connect(self.handle_find_text_finished)
+
+ focus_url_line_edit_action = QAction(self)
+ self.addAction(focus_url_line_edit_action)
+ focus_url_line_edit_action.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_L))
+ focus_url_line_edit_action.triggered.connect(self._focus_url_lineEdit)
+
+ self.handle_web_view_title_changed("")
+ self._tab_widget.create_tab()
+
+ @Slot(str)
+ def _show_status_message(self, m):
+ self.statusBar().showMessage(m)
+
+ @Slot(QUrl)
+ def _url_changed(self, url):
+ self._url_line_edit.setText(url.toDisplayString())
+
+ @Slot()
+ def _address_return_pressed(self):
+ url = QUrl.fromUserInput(self._url_line_edit.text())
+ self._tab_widget.set_url(url)
+
+ @Slot()
+ def _focus_url_lineEdit(self):
+ self._url_line_edit.setFocus(Qt.ShortcutFocusReason)
+
+ @Slot()
+ def _new_tab(self):
+ self._tab_widget.create_tab()
+ self._url_line_edit.setFocus()
+
+ @Slot()
+ def _close_current_tab(self):
+ self._tab_widget.close_tab(self._tab_widget.currentIndex())
+
+ @Slot()
+ def _update_close_action_text(self):
+ last_win = len(self._browser.windows()) == 1
+ self._close_action.setText("Quit" if last_win else "Close Window")
+
+ def sizeHint(self):
+ desktop_rect = QGuiApplication.primaryScreen().geometry()
+ return desktop_rect.size() * 0.9
+
+ def create_file_menu(self, tabWidget):
+ file_menu = QMenu("File")
+ file_menu.addAction("&New Window", QKeySequence.New,
+ self.handle_new_window_triggered)
+ file_menu.addAction("New &Incognito Window",
+ self.handle_new_incognito_window_triggered)
+
+ new_tab_action = QAction("New Tab", self)
+ new_tab_action.setShortcuts(QKeySequence.AddTab)
+ new_tab_action.triggered.connect(self._new_tab)
+ file_menu.addAction(new_tab_action)
+
+ file_menu.addAction("&Open File...", QKeySequence.Open,
+ self.handle_file_open_triggered)
+ file_menu.addSeparator()
+
+ close_tab_action = QAction("Close Tab", self)
+ close_tab_action.setShortcuts(QKeySequence.Close)
+ close_tab_action.triggered.connect(self._close_current_tab)
+ file_menu.addAction(close_tab_action)
+
+ self._close_action = QAction("Quit", self)
+ self._close_action.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Q))
+ self._close_action.triggered.connect(self.close)
+ file_menu.addAction(self._close_action)
+
+ file_menu.aboutToShow.connect(self._update_close_action_text)
+ return file_menu
+
+ @Slot()
+ def _find_next(self):
+ tab = self.current_tab()
+ if tab and self._last_search:
+ tab.findText(self._last_search)
+
+ @Slot()
+ def _find_previous(self):
+ tab = self.current_tab()
+ if tab and self._last_search:
+ tab.findText(self._last_search, QWebEnginePage.FindBackward)
+
+ def create_edit_menu(self):
+ edit_menu = QMenu("Edit")
+ find_action = edit_menu.addAction("Find")
+ find_action.setShortcuts(QKeySequence.Find)
+ find_action.triggered.connect(self.handle_find_action_triggered)
+
+ find_next_action = edit_menu.addAction("Find Next")
+ find_next_action.setShortcut(QKeySequence.FindNext)
+ find_next_action.triggered.connect(self._find_next)
+
+ find_previous_action = edit_menu.addAction("Find Previous")
+ find_previous_action.setShortcut(QKeySequence.FindPrevious)
+ find_previous_action.triggered.connect(self._find_previous)
+ return edit_menu
+
+ @Slot()
+ def _stop(self):
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.Stop)
+
+ @Slot()
+ def _reload(self):
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.Reload)
+
+ @Slot()
+ def _zoom_in(self):
+ tab = self.current_tab()
+ if tab:
+ tab.setZoomFactor(tab.zoomFactor() + 0.1)
+
+ @Slot()
+ def _zoom_out(self):
+ tab = self.current_tab()
+ if tab:
+ tab.setZoomFactor(tab.zoomFactor() - 0.1)
+
+ @Slot()
+ def _reset_zoom(self):
+ tab = self.current_tab()
+ if tab:
+ tab.setZoomFactor(1)
+
+ @Slot()
+ def _toggle_toolbar(self):
+ if self._toolbar.isVisible():
+ self._view_toolbar_action.setText("Show Toolbar")
+ self._toolbar.close()
+ else:
+ self._view_toolbar_action.setText("Hide Toolbar")
+ self._toolbar.show()
+
+ @Slot()
+ def _toggle_statusbar(self):
+ sb = self.statusBar()
+ if sb.isVisible():
+ self._view_statusbar_action.setText("Show Status Bar")
+ sb.close()
+ else:
+ self._view_statusbar_action.setText("Hide Status Bar")
+ sb.show()
+
+ def create_view_menu(self):
+ view_menu = QMenu("View")
+ self._stop_action = view_menu.addAction("Stop")
+ shortcuts = []
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Period))
+ shortcuts.append(QKeySequence(Qt.Key_Escape))
+ self._stop_action.setShortcuts(shortcuts)
+ self._stop_action.triggered.connect(self._stop)
+
+ self._reload_action = view_menu.addAction("Reload Page")
+ self._reload_action.setShortcuts(QKeySequence.Refresh)
+ self._reload_action.triggered.connect(self._reload)
+
+ zoom_in = view_menu.addAction("Zoom In")
+ zoom_in.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Plus))
+ zoom_in.triggered.connect(self._zoom_in)
+
+ zoom_out = view_menu.addAction("Zoom Out")
+ zoom_out.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Minus))
+ zoom_out.triggered.connect(self._zoom_out)
+
+ reset_zoom = view_menu.addAction("Reset Zoom")
+ reset_zoom.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_0))
+ reset_zoom.triggered.connect(self._reset_zoom)
+
+ view_menu.addSeparator()
+ self._view_toolbar_action = QAction("Hide Toolbar", self)
+ self._view_toolbar_action.setShortcut("Ctrl+|")
+ self._view_toolbar_action.triggered.connect(self._toggle_toolbar)
+ view_menu.addAction(self._view_toolbar_action)
+
+ self._view_statusbar_action = QAction("Hide Status Bar", self)
+ self._view_statusbar_action.setShortcut("Ctrl+/")
+ self._view_statusbar_action.triggered.connect(self._toggle_statusbar)
+ view_menu.addAction(self._view_statusbar_action)
+ return view_menu
+
+ @Slot()
+ def _emit_dev_tools_requested(self):
+ tab = self.current_tab()
+ if tab:
+ tab.dev_tools_requested.emit(tab.page())
+
+ def create_window_menu(self, tabWidget):
+ menu = QMenu("Window")
+ self._next_tab_action = QAction("Show Next Tab", self)
+ shortcuts = []
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BraceRight))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_PageDown))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BracketRight))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Less))
+ self._next_tab_action.setShortcuts(shortcuts)
+ self._next_tab_action.triggered.connect(tabWidget.next_tab)
+
+ self._previous_tab_action = QAction("Show Previous Tab", self)
+ shortcuts.clear()
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BraceLeft))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_PageUp))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BracketLeft))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Greater))
+ self._previous_tab_action.setShortcuts(shortcuts)
+ self._previous_tab_action.triggered.connect(tabWidget.previous_tab)
+
+ self._inspector_action = QAction("Open inspector in window", self)
+ shortcuts.clear()
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.SHIFT | Qt.Key_I))
+ self._inspector_action.setShortcuts(shortcuts)
+ self._inspector_action.triggered.connect(self._emit_dev_tools_requested)
+ self._window_menu = menu
+ menu.aboutToShow.connect(self._populate_window_menu)
+ return menu
+
+ def _populate_window_menu(self):
+ menu = self._window_menu
+ menu.clear()
+ menu.addAction(self._next_tab_action)
+ menu.addAction(self._previous_tab_action)
+ menu.addSeparator()
+ menu.addAction(self._inspector_action)
+ menu.addSeparator()
+ windows = self._browser.windows()
+ index = 0
+ title = self.window().windowTitle()
+ for window in windows:
+ action = menu.addAction(title, self.handle_show_window_triggered)
+ action.setData(index)
+ action.setCheckable(True)
+ if window == self:
+ action.setChecked(True)
+ index += 1
+
+ def create_help_menu(self):
+ help_menu = QMenu("Help")
+ help_menu.addAction("About Qt", qApp.aboutQt) # noqa: F821
+ return help_menu
+
+ @Slot()
+ def _back(self):
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.Back)
+
+ @Slot()
+ def _forward(self):
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.Forward)
+
+ @Slot()
+ def _stop_reload(self):
+ a = self._stop_reload_action.data()
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.WebAction(a))
+
+ def create_tool_bar(self):
+ navigation_bar = QToolBar("Navigation")
+ navigation_bar.setMovable(False)
+ navigation_bar.toggleViewAction().setEnabled(False)
+
+ self._history_back_action = QAction(self)
+ back_shortcuts = remove_backspace(QKeySequence.keyBindings(QKeySequence.Back))
+
+ # For some reason Qt doesn't bind the dedicated Back key to Back.
+ back_shortcuts.append(QKeySequence(Qt.Key_Back))
+ self._history_back_action.setShortcuts(back_shortcuts)
+ self._history_back_action.setIconVisibleInMenu(False)
+ back_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoPrevious,
+ QIcon(":go-previous.png"))
+ self._history_back_action.setIcon(back_icon)
+ self._history_back_action.setToolTip("Go back in history")
+ self._history_back_action.triggered.connect(self._back)
+ navigation_bar.addAction(self._history_back_action)
+
+ self._history_forward_action = QAction(self)
+ fwd_shortcuts = remove_backspace(QKeySequence.keyBindings(QKeySequence.Forward))
+ fwd_shortcuts.append(QKeySequence(Qt.Key_Forward))
+ self._history_forward_action.setShortcuts(fwd_shortcuts)
+ self._history_forward_action.setIconVisibleInMenu(False)
+ next_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoNext,
+ QIcon(":go-next.png"))
+ self._history_forward_action.setIcon(next_icon)
+ self._history_forward_action.setToolTip("Go forward in history")
+ self._history_forward_action.triggered.connect(self._forward)
+ navigation_bar.addAction(self._history_forward_action)
+
+ self._stop_reload_action = QAction(self)
+ self._stop_reload_action.triggered.connect(self._stop_reload)
+ navigation_bar.addAction(self._stop_reload_action)
+
+ self._url_line_edit = QLineEdit(self)
+ self._fav_action = QAction(self)
+ self._url_line_edit.addAction(self._fav_action, QLineEdit.LeadingPosition)
+ self._url_line_edit.setClearButtonEnabled(True)
+ navigation_bar.addWidget(self._url_line_edit)
+
+ downloads_action = QAction(self)
+ downloads_action.setIcon(QIcon(":go-bottom.png"))
+ downloads_action.setToolTip("Show downloads")
+ navigation_bar.addAction(downloads_action)
+ dw = self._browser.download_manager_widget()
+ downloads_action.triggered.connect(dw.show)
+
+ return navigation_bar
+
+ def handle_web_action_enabled_changed(self, action, enabled):
+ if action == QWebEnginePage.Back:
+ self._history_back_action.setEnabled(enabled)
+ elif action == QWebEnginePage.Forward:
+ self._history_forward_action.setEnabled(enabled)
+ elif action == QWebEnginePage.Reload:
+ self._reload_action.setEnabled(enabled)
+ elif action == QWebEnginePage.Stop:
+ self._stop_action.setEnabled(enabled)
+ else:
+ print("Unhandled webActionChanged signal", file=sys.stderr)
+
+ def handle_web_view_title_changed(self, title):
+ off_the_record = self._profile.isOffTheRecord()
+ suffix = ("Qt Simple Browser (Incognito)" if off_the_record
+ else "Qt Simple Browser")
+ if title:
+ self.setWindowTitle(f"{title} - {suffix}")
+ else:
+ self.setWindowTitle(suffix)
+
+ def handle_new_window_triggered(self):
+ window = self._browser.create_window()
+ window._url_line_edit.setFocus()
+
+ def handle_new_incognito_window_triggered(self):
+ window = self._browser.create_window(True)
+ window._url_line_edit.setFocus()
+
+ def handle_file_open_triggered(self):
+ filter = "Web Resources (*.html *.htm *.svg *.png *.gif *.svgz);;All files (*.*)"
+ url, _ = QFileDialog.getOpenFileUrl(self, "Open Web Resource", "", filter)
+ if url:
+ self.current_tab().setUrl(url)
+
+ def handle_find_action_triggered(self):
+ if not self.current_tab():
+ return
+ search, ok = QInputDialog.getText(self, "Find", "Find:",
+ QLineEdit.Normal, self._last_search)
+ if ok and search:
+ self._last_search = search
+ self.current_tab().findText(self._last_search)
+
+ def closeEvent(self, event):
+ count = self._tab_widget.count()
+ if count > 1:
+ m = f"Are you sure you want to close the window?\nThere are {count} tabs open."
+ ret = QMessageBox.warning(self, "Confirm close", m,
+ QMessageBox.Yes | QMessageBox.No,
+ QMessageBox.No)
+ if ret == QMessageBox.No:
+ event.ignore()
+ return
+
+ event.accept()
+ self.about_to_close.emit()
+ self.deleteLater()
+
+ def tab_widget(self):
+ return self._tab_widget
+
+ def current_tab(self):
+ return self._tab_widget.current_web_view()
+
+ def handle_web_view_load_progress(self, progress):
+ if 0 < progress and progress < 100:
+ self._stop_reload_action.setData(QWebEnginePage.Stop)
+ self._stop_reload_action.setIcon(self._stop_icon)
+ self._stop_reload_action.setToolTip("Stop loading the current page")
+ self._progress_bar.setValue(progress)
+ else:
+ self._stop_reload_action.setData(QWebEnginePage.Reload)
+ self._stop_reload_action.setIcon(self._reload_icon)
+ self._stop_reload_action.setToolTip("Reload the current page")
+ self._progress_bar.setValue(0)
+
+ def handle_show_window_triggered(self):
+ action = self.sender()
+ if action:
+ offset = action.data()
+ window = self._browser.windows()[offset]
+ window.activateWindow()
+ window.current_tab().setFocus()
+
+ def handle_dev_tools_requested(self, source):
+ page = self._browser.create_dev_tools_window().current_tab().page()
+ source.setDevToolsPage(page)
+ source.triggerAction(QWebEnginePage.InspectElement)
+
+ def handle_find_text_finished(self, result):
+ sb = self.statusBar()
+ if result.numberOfMatches() == 0:
+ sb.showMessage(f'"{self._lastSearch}" not found.')
+ else:
+ active = result.activeMatch()
+ number = result.numberOfMatches()
+ sb.showMessage(f'"{self._last_search}" found: {active}/{number}')
+
+ def browser(self):
+ return self._browser
diff --git a/examples/webenginewidgets/simplebrowser/certificateerrordialog.ui b/examples/webenginewidgets/simplebrowser/certificateerrordialog.ui
new file mode 100644
index 000000000..a97f25b6e
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/certificateerrordialog.ui
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CertificateErrorDialog</class>
+ <widget class="QDialog" name="CertificateErrorDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>370</width>
+ <height>141</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>20</number>
+ </property>
+ <property name="rightMargin">
+ <number>20</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="m_iconLabel">
+ <property name="text">
+ <string>Icon</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_errorLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Error</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="m_infoLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>If you wish so, you may continue with an unverified certificate. Accepting an unverified certificate mean you may not be connected with the host you tried to connect to.
+
+Do you wish to override the security check and continue ? </string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::No|QDialogButtonBox::Yes</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>CertificateErrorDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>CertificateErrorDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/COPYING b/examples/webenginewidgets/simplebrowser/data/3rdparty/COPYING
new file mode 100644
index 000000000..220881da6
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/COPYING
@@ -0,0 +1 @@
+The icons in this repository are herefore released into the Public Domain.
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/dialog-error.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/dialog-error.png
new file mode 100644
index 000000000..cdd95bade
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/dialog-error.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.png
new file mode 100644
index 000000000..5542948bc
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.png
new file mode 100644
index 000000000..bf973fedc
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.png
new file mode 100644
index 000000000..a68e2db77
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.png
new file mode 100644
index 000000000..c37bc0414
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.png
new file mode 100644
index 000000000..e7a8d1722
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json b/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json
new file mode 100644
index 000000000..d81f5bf23
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json
@@ -0,0 +1,24 @@
+{
+ "Id": "simplebrowser-tango",
+ "Name": "Tango Icon Library",
+ "QDocModule": "qtwebengine",
+ "QtUsage": "Used in WebEngine SimpleBrowser example.",
+
+ "QtParts": [ "examples" ],
+ "Description": "Selected icons from the Tango Icon Library",
+ "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library",
+ "Version": "0.8.90",
+ "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz",
+ "LicenseId": "urn:dje:license:public-domain",
+ "License": "Public Domain",
+ "LicenseFile": "COPYING",
+ "Copyright": "Ulisse Perusin <uli.peru@gmail.com>
+Steven Garrity <sgarrity@silverorange.com>
+Lapo Calamandrei <calamandrei@gmail.com>
+Ryan Collier <rcollier@novell.com>
+Rodney Dawes <dobey@novell.com>
+Andreas Nilsson <nisses.mail@home.se>
+Tuomas Kuosmanen <tigert@tigert.com>
+Garrett LeSage <garrett@novell.com>
+Jakub Steiner <jimmac@novell.com>"
+}
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/text-html.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/text-html.png
new file mode 100644
index 000000000..a896697d7
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/text-html.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.png
new file mode 100644
index 000000000..606ea9eba
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/AppLogoColor.png b/examples/webenginewidgets/simplebrowser/data/AppLogoColor.png
new file mode 100644
index 000000000..2a4971782
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/AppLogoColor.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/ninja.png b/examples/webenginewidgets/simplebrowser/data/ninja.png
new file mode 100644
index 000000000..e5d7b6fd7
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/ninja.png
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/data/rc_simplebrowser.py b/examples/webenginewidgets/simplebrowser/data/rc_simplebrowser.py
new file mode 100644
index 000000000..5d5a3736a
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/rc_simplebrowser.py
@@ -0,0 +1,1391 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.5.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x06\xdf\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x06\x96IDATX\x85\xe5\x97[l\x1cW\
+\x19\xc7\x7f3\xb3\xde\xab\xd7^{}\x8d\xe38I\xeb\
+]\xe7\x82\x1b\xa3\xdaN\xda\x105\x12\x17\x89\x0a\x19!\
+\x11\x10O\x01E\x02a\xc4\xe5\xa5\xe2\x01\xa1\xc0\x0b\xbc\
+\x80DE@\x88\x16\xfc\xd0\x82\x84J\xaa\x906\x94\xf4\
+B\xd2`\x92Z\xd8M\x1c\xdb\x91/\xc9:\xb1\x1dg\
+\xed\xb5\xd7;3;\xb3\xbbs\xe3!\xde\xcd\xda^\xbb\
+N%\x9e\xf8\xa4\xa39\xe7\xcc\xd9\xf3\xfd\xe6\xff}\xe7\
+\xb2\xf0\xffn\xc2\xfa\x8e3g\xce|\xc9\xe5r\xf5\x01\
+AI\x92\x10E\x11\xdb\xb6\xb1,\xabPL\xd3,<\
+\x8b\xeb[\xf5\x01\x8ai\x9a'\xfb\xfa\xfa\xce\x16\xfbs\
+m \x12\x84\x97O\x9d:\x15\x5c\xadc\x9a&.\x97\
+\x0b\xc7q\xd6\x8c+no\xb3\x1e\xec\xed\xed}\x19\xd8\
+\x1a\xc0\xb2\xac\x10@,\x16C\x10\x04,\xcb\x22\x10\x08\
+\x90\xcb\xe5\xf2\x80\x08\xc2#\xe1\x8a\xeb\xa5\xday\x90\xba\
+\xba:r\xb9\x5ch\xfd\xbb\x0d\x00\x8e\xe3\x14\x9c\xe4'\
+[_/\xf5\xdc\xcc\xf9G\xd9\x06\x00\xdb\xb6\xd78-\
+v455Ux\xbf\x1d\xa7\x92$\x11\x89D\xd6|\
+\xdc\xb6\x00\xf2*\xac\x07imm\xfd\xdf+`Y\xd6\
+\x9a\x09\x8bU\xf88\x0aD\xa3\xd1B{\xdb\x0a\x94r\
+.\x08\x02\x91H\xe4c)\x90\xef\xdf\x16@^\x81R\
+I899\x89eY\xdb\x96]\x92$\xda\xda\xda\x0a\
+\xed\xc7\x02\x00\x18\x1a\x1a\xa2\xa3\xa3\xa3\x00\x90\x97\xb3\x18\
+\xce\xb2\x1c\x06\x06\xc6\xb9\xd2\x7f\x8b\xf1\x899\x94\x94\x0a\
+@\xb0\xb2\x9ch\xa4\x89d\x12:;#\x8f\x07\x90O\
+\xc2\xae\xae\xae5\xeb\x7fbbb\x8d\x02\xa3\xa3s\xfc\
+\xed\x8d\x9b\xb4}\xe2I\x9e=\xd6\xc17\xbe\xd9CM\
+\xc8\x8f\x961\x89\xcd+\x0c\x8f\xde\xe5\xfc\xdb#\xfc\xee\
+\xf7\xff\xe0\x07\xdf\xfb\xe2\xe3\x87\xc0\xb6\xed5\x12G\xa3\
+\xd1B\xfb\x8f}\xefp}d\x8e\x9f\xfc\xec[\xb4\xed\
+\xadeA\xce\xd2X\xe9&\x18,\xe7R\xff\x00\xde\xda\
+'\xe8:\xbc\x9f\xe7\x8e\xeeg|r\x96\xdf\xbet\x81\
+t\xaa\x1c\x1en\xff\x05\x12i=\xc0\xd1\xa3GO\x1f\
+?~\x1cUU\x11\x04\x01\xc7qp\xbb\xdd8\x8e\xc3\
+\xc4\xc4\x04\x0b\x0b\x0b\xbc\xf2\xea%\xe6\x97\x0c^\xfc\xc5\
+),\xc7d\xe2\xde<9\x13ty\x99\xc1[w\xf1\
+\x94\xd7\x90s$4E!(\xa6y\xfa\xc0.:\xbb\
+[\xf9`h\x9e``\x9fwvf\xe0\x9d\xbc?q\
+\xab\x10\xacO\xc2\xb6\xb66\x0c\xc3GlF\xe5\x97?\
+?Il6\x8e\x91\xcb\xd2T\xe5\xa7\xbe\xa1\x81@\xdd\
+^Z\xf7\xb5SU\xdb\x80O\xc8\x81\xa9\x12_\x92q\
+{\xdc47\x86\xf9\xe9\x8fNP\x11\xae\xea\xed\xee\xee\
+\xfd\xcc\xa6\x00\xab'WI\x1b\x1b\xbb\xc5\x8bg\xde\xe0\
+\x85\x17\xbe\x8c\xdf\xe7\xe5`\xa4\x85=\xcd\x0d\xcc\xdc\x8f\
+\xa3\xa7\x15\xdc\x1e\x0fn\xb7\x0b\x97K\xc2\xb2\xa1s\xdf\
+.>{\xac\x13\x9f\xd7\x8b(\x8a\x84\xab\x82|\xfd\xe4\
+1\xbf\xe0*\xfb\x0d\x9c\x167U\xa0\xf8\xab\x8b\x8b\xa2\
+\x88\xec\xd8\xd3\xcc\xa1\x83{\x00\xd04\x9d\xcb\xd7\xae\xd3\
+\x1c\xed\xa0\xa2\xaa\x16\xc7\x01Q\x10\x11\x04\x91\xfa\xa0\xc3\
+\xc5\xf7\xff\xc3\xb9\xb7\xfb\x99\xbc\x1d\xc3\xe5ra\x18\x06\
+\x9f\xea\xde'\xee\xd8\xdd\xd4p\xe4\xc8b\xcf\x96\x00\xa5\
+\xec\xf2\x951>\xf7\xe9C\x88\xab\xb9\xf1\xd6\x95AZ\
+\x9fz\x06\xb7\xc7\x87\x03\xd8\xf9\xe28,\xa5\xd2\x1c\xeb\
+n\xc7\xe7\xf518\x95\xe0\xdd\xab7V\xe77\xe9\xea\
+l\xf6#I_\x83\x8fX\x86\xeb\xf3\xe0\xe6H\x8co\
+\xf7\xf6p\xfe\xbd\x0fX\xd2%\x1a[\x9e\x22\xa5\x19T\
+\x07\xcapx\x98\xde\x86i\x91^\x9e\xe7\xf0\xa1}\x00\
+45\xd6\x030tc\x0c5\xad142ISS\
+5\xc0\xd1\x92\x0a\x14\xe7\xc0\xc0\xc0\x00\xa2\xf8h\x88\xa6\
+f\xf0\xfb=\xd4\x84*0]\xe5d\x0d\x1b9\x9d%\
+\xa1d\xd1\xb3&Z\xc6@\xd5\x0d|\x92\xb1A\xbd'\
+v\xef\xe4\xda\xf5qv\xd4V\x92\x16\x83\x0e\xb6\x13\xde\
+T\x81\xbc\x1d9r\x84l6[P\x00\xc7\xe1\xce\x03\
+\x9d\xe9\xb8L\xb8\x0c|Y\x1bM\x13\xd0\xcb\x1bH\xba\
+$D\xc0\xb4l\x02\x99\xf4\x9a9\x1d\xc7\xc14\x0d\x1a\
+\xc3A^\xbb4F\xa4\xad\xbd\xf0n\xd3\x10\xe4\xeb\xc5\
+a\xa8\xa8\x0a\xb2\xb0\xac\x90\x96jXPT\xbes<\
+\x8a$I\x5c\xb84\xc0\xb2\xa7\x05\xdbq0\x0c\x8bL\
+\xc6\xcf\xfc\x9b\x97I\xach|\xf5\xf9gp\xbb$t\
+]\xa7\xaa2\x80fH\xa8)\xcdD\x14\x92%C\xb0\
+\xfe8.\xb6\x9dM!F\xc7\xeeQ\xee\x91\xa8\xf2\x9a\
+H\xd2\xc3}\xec\xf3\xcfu\x11\x0d\xae\xa0,\xddgE\
+\xd1Y\xd1L\xc6\xe4jb\xd9:&\xee\xcc\xa0\xaa*\
+\xe9t\x9a\xd7/^#\x18\xaagfnQ\x04\xfeU\
+\x12 \x9f\x03\xa5N\xb9/<\x7f\x98[\x1f\x8eS\xe9\
+\x97\x98\x8e\xab\x18\xc6\xa3Xw\x1c\x8cb\xa4\xe6I*\
+\x1a+\x8aNJ\xd5\x91\xd3\x19>\x1c\x9fE\x96e\x14\
+E!\xb1\x92\xa61\x1c`z\xe4v\x06\xcb\xfa\xd3\xa6\
+\x0a\x94:4\x00**\x1cR\xf1\x07\xdc\x1a\x9f!\x1c\
+\xae\xe5W\xaf\xbc\x85,\xcbh\x9aF6\x9b%\xb6\x0c\
+\xcb\xb2\xbe\x0a\x90ANg0s\x19R\xa9\x143\xb3\
+\xf7\x09U\xd71\x1fW\xf4\xa5\xb9\x07\xf1\xabWk\xcf\
+m\x19\x82R\x10\x07\x0e\xec\xe7\xfb\xdf\xed\xe1\xfds\xef\
+Q\xe9\x11\xc8z\x9bX\x5c\x5c$\x91H\xf0\xeb\xbe\xb3\
+$r>\x92\xb2F\xb2\x08b\xe4n\x92d2I|\
+)EEM37\xde\xfd\xb7\xa3)\xf1\x1f\xc2i{\
+\xcb\x10lf\xed\xed\xbb\xf9d\xfb\x0e\xce\xbdz\x81\x1d\
+\x95ed\xb29b\xd3\xf7\xb8r\xc7&\xa9fI\xca\
+:IE#\xa9\xe8\xa4U\x85\xb8\x22\xb2\xb8,\xa3\xda\
+!\xfa\xff\xdeO\xe2A\xec\xdc\xf0\xf0\x9fo\x02e\xb0\
+q\x15\x04EQ\x94\x1d\xc7\xa9\x08\x85B\x05E\xca\xca\
+\xca\x0a\x8a\x98\xa6\xc9WN<\x8b\xfa\x87\x8b\x9c}\xe9\
+\xafd{\x9e\xc6[\x19b\x7fd7s\x09\x95\xb4\x96\
+\xc5\xe3\x18x\x03\x02\xf5\x95\xe54\x84\x1b\x19\xbe\xaf2\
+\xfc\xcfk\xac,\x8d\x9b\xc3\xd7_{\x1d\xf0\xf0p\xd3\
+\x5c\xf3\xd7,\x08\xd4vww\x9fhii\xf91\xe0\
+\xdbL\x85\x5c.\x87\xae\xebh\x9a\x1b\xc3\xdeIC\xcb\
+.vF\x9f\xa4\xae!\x84?\xe0\xc3\xb6\x1d\x14Ye\
+!\x9ebn|\x8a\xc4\xec,\x8e\x15\xb3\xe6\xe6F\xff\
+2;;{\x1e\x18\x04\xa6\x00\xbb\x18\xc0\x0d\xd4\x03a\
+ \xb4J\xb9\xe1\xbe\xb0n|\x95(z\xaa\x9b\x9a\x0e\
+u\x86\xaa\xf7\x1e\xf2zC;]\x92\xe8\x050-;\
+\xa3\xeb\xcb\x0f\x92K\xb7G\xe7\xe7\x87\x07m;'\x03\
+w\x81I`\x1aP\xd7+\x907\x0f\xe0]u\xb0\x9d\
+\x8b\xbe\xb8:>\x00\xf8W\x7f'\x01\x16\x90\x014@\
+Yu\x98\x01\xb2\x14\xdd\x88\xfe\x0b\xd2\xfcz\x18\x9f\x9f\
+e\xa7\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x04\xc3\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x04zIDATX\x85\xed\x96\x7fhUe\
+\x18\xc7?\xef{\xceq\xded\xea\xa6\xce\x143S\x0b\
+\x8a~\x97.\xe7(*\x13-\x09\xcb\xfaC\x8a2#\
+(\x0cB7#\x09\xc2\xa0\xa2\xc8\xfe\x91D#,\x84\
+\xe5\x0f\xa4\x85\x18e\xaci\xb36\xdcr*\xe6\xaf\xd0\
+\xcd\x99S7\x9b\xda\xb6\xbb\xbb{~\xbc\xef\xdb\x1f\xf7\
+\x9cyw\xdd\xe6\x9c\xe0_{\xe0\xb9\xcf\xcb=\xe7y\
+\xbe\xdf\xf3}\x9e\xf3\xbe\x07\x06m\xd0\x06h\x05E\xb6\
+\x99Ql=w\xbdu\xe4\xf5$O\x1e{\xe7\xc6\xc2\
+b{% \x06Z\xc3\x1ah\xe2-\x05r\xe5\x87\xaf\
+}k\xc7\x93\xad\x8f0\xb5nz\xdel\xbd\xed\xdco\
+\xf8\xd7Z\xe7\xba\x14\x90R0\xbfp\xf1\xd0\x05\x8f\xbd\
+1'\x96p\xf6\xe5\xbf\xcb\x84\x1bJ\xc0S.G\x9b\
+\xab\xb8gj\xbe\xf3\xfa\xbc\xf7o\x1ffe\x1f,(\
+\xb6g\xdc0\x02Z\x07$\x838G\x9b*\x199b\
+\xb8|\xfb\x85\x8frF\x0d\x1f[^\xb0\xccZ\xd4\xdf\
+\x1a=\x0eOA\x91m\x10t\xf6\x99i\x88}\xfa\xd6\
+w\x1ch,\xc3S.\xc6h\xc6dOdLl\x12\
+\x9b\xcb\xd6$\xeb\x9b\x0e\x7f=\xbeA-\xdd\xba\x155\
+ \x02\x1f,^\x8be9H$B\x08\x04\x02\xa2\x18\
+fJa\xf3\xe7\xa9\x1f\xf1\xb5\x8b\xd6\x0a\x83&;k\
+4SF?HyMi\xb2\xfa\xe8\xce=I\x19\xcc\
+\xaf\xfd\x8c\xd6\xde\x08\xd8\xbdS3\xec>\xb1\x09)\xac\
+.\x17B\xa4\x91\xb9,\x85\xd6\x1am\x0c\xda(Z:\
+N\xd3\x9e\xbc\xc0\xa3\x0f\xcd\x1d:*gl\xe1\xcfU\
+\x9b\xff\xca_.\x9f\xac\xfe\xdc=\xde\x13L\xaf3\xa0\
+\x8c\xc2W\x1e\xbe\xf6\x08\xb4\x87\xaf]|\x95Z\x07x\
+(<t\x18\x95\xf0P\xc2E\x91\xba\xde\xee^\xa0\xe6\
+\xd4v&N\xb8\xcd~i\xce\x92\x09\xc3\x9c\xac\xda\x99\
+E\xf6S\xd7D\xc0\x18\x9d\x02S\x1e\xber\xf1\xb5\x1b\
+\x82\x84.|\x02\xe1\xa1\x84\x8f\x16>\x0a\x97\xb8\xdf\xc2\
+\xc5d#\xe7;Nr\xae\xad\x8e\xdd\xc77\xe1Yq\
+\xf1\xea\xbcw\xb2sF\xe4m\x9b\xb9\xdcY\xda\xef\x16\
+h\xad\x08\xb4\x87@\x22\xa5\xc4\x16V\x8a\xaf\x94a\xff\
+\x05\xca\x04tx\x97hK\xb6\x90\xf0\xdb\xd1J\xa1\x95\
+Ish\xfe\xbb\x81;\xf2\xa6\xf3\xf2\xdc%\xb1m\x15\
+%\x1fS||V\xe5\xaa\xe0\x99\xab\x12\xf0\xb5K\xc2\
+k\xc3\x926\x8e\xe3\x10h\x83\x09\x14F\x05\x04\xc6\xc5\
+\xd5q<\x95\xc0\x08\x00\x83\xb4\x09g\x04R?\x1ac\
+\x0cFC\xa0=<\xed\xa2M \x80)\xfdR \xe1\
+\xb5\xd3\xd4V\x8f\x90`9\x12\xcb\x16X\xb6@\xda\x02\
+i\x09\xa4\x04\xe9\xa4\xde\x09\x83\xc0h\x83\x8e\xc0\x85\x01\
+#\xc0\x08\xee\x1e\xff\x04y\xb1\xc9l\xd9\xb1\xae\xb3\xb5\
+\xfd\xe2\xea\xcaU\xc1\x8a\xab\x11\xe86\x17\xd2\x12H+\
+\x8c\xb6\xc0\x0a\xa3\x94\x02\x11\xdei\x0c\x18\x1d)`\x00\
+\x83-b<<\xe9Y\xfc\xb86\xdf\x97m\xe8Lt\
+t\xbeY\xbdZm\x02L_\x04D\xfa\x7fB\xa6\xf6\
+{)\xc3\xa7\x8e\xc0\xbbT\x10!\x01\x83\xd1\xe1\x93\x03\
+\xc3\x9c\x5c\xa6\xdd\xfc<\xf5'\xeb\x82\x8a\x9a\xb2\xb6\xd6\
+F\xb3\xe0\xd0fUK\xea\xf0S\xe9$2\x09\xc8t\
+\x05^|`E\x17P\xa4\x82\x90)\xf9\x85L\xf5\xbb\
+\xec\xcc\x97\xf8\x81\x8bV\xa9\x9acb\x13\xb97\xf7i\
+*\xf7\xee\xf6\x0e\x1e\xa9\xad;\xf5G\xb0\xf0L\x0dg\
+\xc3\xba\xe1$_\xde\x1d{j\x81\x00X\xbb\xe5\x93\x1e\
+.u\xb7\xe2%+\xb0\x1c\x81\x0e\x87\xef\xd6\xec\xfb\x98\
+\x14\xcbg\xfb/\xa5\xc9\x86\xd3\x0d;\x8flT\xcb\xda\
+[\x88\xa7\xd7%c\xf7\xcd$`\x00]\xf5E0\x1c\
+\x18\x028\xa1\xa7\xaf\x1d\xc0),\xb6+\x10dY\x96\
+@\x1b\x8b\xbbF>N\xcc\x1dGI\xe9\x06\xf7\xdf\x7f\
+.\xae\xa9\xfdF\xad\x03\xfc\xb0f\xbaw;\x1b2\x09\
+\xe8\xd0\x15\x10\x84l#\xef\x22\xd8UD\x08\x86f\xdd\
+\xc4\xfd\xb9\xb3\xb8t\xb6\xd3\x94\xfc\xb4>q\xeeXr\
+\xe9\xb1\x1f\xd4\xae0?r\x95\xb6\xees\x08I\x03\xbe\
+B\x99\xb4B\x16\x80-\x860-g!\xfb\xf7\xef\x0b\
+v\xfd\xfekK\xe3>\xfd\xca\xc9ru\x22\x03\xdcO\
+\x8bW|1\xf5D\xc0\x00^\x08\x18\x01\x07\xe1\xbd\x16\
+\xd1 \x09\x8c\xf2\xa1\xac\xa2\xdc=pd\xef\xc1\xfa\x1d\
+\xfe\xa2\xa6\xc3\xfc\x97A4\x8a.\xf4|,\xf7\xe7c\
+\xd2\xbe\x02\x1cdA\x91}!/w\x9c\xdft\xfel\
+\xc9\xa1\xf5\xea\xbdx\x9c\x80\xcb-\x8cH\xfb\xbd\x01\xf7\
+\xa5@\xa6ERF\xb3\xd0\xf5\xaa677-\xdf\xb3\
+Z}E\xf7\x01\x8bT\x1b\xb4A\xeb\x97\xfd\x0f\xcc\x13\
+\x1e)\xc9\x8aX\x89\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x04\xef\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x04\xa6IDATX\x85\xed\x97]\x88UU\
+\x14\xc7\x7f{\x9fs\xef\x5c?\x82RqP\xfb\x18\xc3\
+\xac\x87\x14\xcb\xaf\xcaJK\xc8\x87\x12\xc6@\xa3D\x83\
+\x0a\x83\xa0\x82B{\xe9a\xc2zP{\xec\xc1\x08\x09\
+#$\x13\x95\x88P\xc8\xaf\xc1\x994t*1\xa7\xd4\
+1u\x1amrR\x9bf\xee\xd79g\xaf\xd5\xc3\xb9\
+\xf7:\xe3\xdc\x9b\xf7\x12C/m\xd8\xac}\xf69\xe7\
+\xbf~g\xed}\xd6:\x07\xfeo\xffq3\xd5\x5c4\
+o\xb5\xd5Z\x85[7HU\xda~\xb5\x82\xef\xad\xfa\
+\x14'\x11A\x94\xc5i\x84\x93\xb8\x8bF\xf1\xb1\x8b-\
+\x186\xed\xdc\x00d\xab\xd2\xad\x1a ty\x8e]\xd8\
+O\x7f\xee\x0a\xa1\xe4\x09%O\xe4r\x84. (\xd8\
+|\x94a\xd1=/U+\x09\x80\xad\xf6BU%\x92\
+\x00\xa7!\x91\x04D. t\x01\xa1\xcb\x17\xc6y\xc2\
+(\x87\x93px\x00\xaai\x82\xd4|O\x0d\x00\xf1>\
+T\x1dl\xe33\xf1\xd8I4\x9c\x00\xa0*(:\x08\
+\xe2\xdaX\x90\xe1\x04P\x95\xd8!J\xfc\xcc\x05\x98\x02\
+P\xe0r\xa5H\x0c\x0b\x80\x14\x9d\xaa E\x18\x8da\
+\x9cD\xe4\xc3t\xcd\xce\xa1\xcckX1\xe9\xa8\x22\xe2\
+P\x04\xac`P\xacQ\x5c\x98##\x97\xf1\x92\xa0j\
+0\xde\x10\x9d\x00HB\xf9\xe444\x0fX\xf3\xcc\xa4\
+\xb1\x937\xbf\xd2\xb86e\xadE\xd4\x15\x9e\xd8\xc5\x8e\
+\xad`\x00!\xa0/\xe8!\xebz\xb1\x09\xb0XT!\
+\x99H\xb1j\xe9\x9a8B\x02\xe24\xb9c\xef\xc7a\
+\xcf\xd5\x8b\xaf\x96{\xae\xb2\xe9\xf2\xe1\xb7\xec\xdaiw\
+>\xf8\xe6\xd3\x8f\xbe8\xe2\xfb\xae=\xa4\xc3^\x94\x10\
+gB\x1cy\xaed\xbb\xe8\x0b\xfe\x00\xab`\x0c&\x0e\
+\x10\xa8\xa2\x0a\x22J\xc2\x8cdA\xc3\x0aZ\xdb\xf6g\
+;:\x7f\xfc\xa8e\xbd\xbc^\xce\x97Wn\xb2\xb3U\
+\x0f\x8c\xb8\xb7k\x8e\xb5~\xc3\xf4\xc9\x8f\xf8\x97\xd2\x1d\
+8\x1b\x12i\x96\x8b\xfd'\xc9\xe9_\xd8\x84\xc1\xf3\x0c\
+\xd63X?\xb6\xc63\x18\x0b\xbe\xe73\xef\x8e\xa5t\
+\x9c=\x1d\x1c?}\xf8\xf0\x84_ty{{\xf9\x1d\
+Zi\x13j&\xa5\xcf\xee\xfbng\xc7\xb9\x8b\xedn\
+j\xfd\x5c\x94\x88\xee\xf4)\x9c\xcd\xe1%\x0c~\xc2\xe0\
+%-~\xd2\xe2'\x8a\xd6\xe0',\xf7Mz\x82\xbe\
+\xde\xb4~{l_wF\xb5q\xdb6\x5c\x05?\x95\
+\xdf\x82\xb6&2.\x90E\xdb\x9b7\xfd\x99\xcf\x05:\
+n\xf4\xad\x84&\x8b\xe7\x9b\xb8\x17 \xfc\x22D\xd2\xe2\
+%-S\xc6\xcdb\xb4\xa9gw\xf3\x8e\xfe\xc8\xc8\xc2\
+\xb6u\xf4V\xf2Qq\x09\x8a\xad\xeb0}\xb7\xcdq\
+{O\xfez|\xc5\xfdw?\x94\x10/\xa0\xdf\xf5`\
+}\x8bW\xea\xf1\x12\x18k\x18?\xaa\x81\xa97\xcd\xe7\
+\xf3/?\xc9f\xb3\xb9\xc6o\xd6\xcb\xd1\x7f\xd2\xbf!\
+\x00@\xe7!~\x1b\xff@x\xfa|\xd7\x99\xa7\x1e\x9b\
+\xb1$\xd1\xeb\xba\x09\xe8\xc3KX\xbc\x84\x89\xado\x18\
+\x9d\x1c\xc3\xcc\xb1K\xd8\xb9kk\xa6\xe7\xf2\xefo\xb7\
+\xbe\xef\xb6\xdcH\xbb*\x00\x80\xae\x16m\xaf\x9f\x9bM\
+^\xba\xdc={\xc1\xf4\xc6\xc4\xa5\xfc\x19\xc4\xe6K\x11\
+\xa8K\xa6\x98=v\x19\xcd\xad\xfbs\x1d\xe7\x7f\xda\xd1\
+\xb2N\xd6T\xa3[5\x00@g\x8b6\xdf<\xe3\xea\
+\xac(\x92\x86Yw=\xeew\xe7~\x06+x\xd62\
+cL#\x1d\xa7\xceF\x87\x8e\x1c<q%\xe5\x16\xf7\
+\x1c\xa8\xbc\xe9\xaeo\xb5\x14#\xcd\xd4\xe9\xf2\x1fN\x1c\
+9s\xee\xdc\xf9h\xfa-O\xa2\x0e\xa6\x8c\x9aO\x7f\
+O\xa0{\x9aw_\xc5D\x8bN4\x11\xd4\xa094\
+\x11\xcdlb\xe4\x88\x1cu\x15)\x94\x89\x16\xef\xe8\xb2\
+\xc5+\xeb&N\xb8\xdd\xf4\xf5\xf7\xb2y\xeb\x87\x12\x06\
+\xe1B\xfc\xe8X\xa5\xfb\xb2)\xf2mMd\xae\x9f\x1f\
+\x92\x8aSi\x9b\xc6\x90+\x8f\x17O\x89hj\xfbW\
+\x9f\xf1\xf2\xf3\xaf\xf1\xc5\xd7[\x5c\x10\xe6=\xeb\xb3\x0b\
+l\x9cm\xca\xa4\x9cT\x9a\x14TS\x0b\x807\x9e\xdb\
+\x90*\xea\x18\x0b\xd6\xc4\xd6X\xc0*\xc6\x98\xd8\x02/\
+4\xae\xf6\xe2\xc8h\x0a\x15\xa4P%E\x04u\xe0D\
+\x10Q>\xd8\xf2N\xd9\xc8\x94\x05\x88$d\xdf\x85\x8d\
+x\xd6\xc3X\xc5\xfa\x03K\xb1\xc3\xa9\xc3\x89\xa0\x02.\
+\x8a\x8b\x95D\x82\x08H\xe4P\x0c\xea\xb4T\x90\x96L\
+[]\xd6yE\x80|\x94a^\xfd\xcak\x13\xa6\x18\
+\x05\x83)F\xc3\x98\xd2\x12\x19S,F\xf1q\x1c\x81\
+xNE\x11)\xad\x89\x0f\x0c\xfal*\x0b\xb0q\xdb\
+\xbb\x15\x89\xffe\xb3\xc4\xd8%\xa2J\x7f/\x16H\x10\
+\x03z\x03\xec\xc0^\x14+\x0a* \x80\xbb\xaeG\x05\
+\x1b\x14\xc6\x83\xb6\xe8\x8d~\x9fL\xc1\xd1@\x08;`\
+\xbexMQT\x0a\xe3\xa2\xe3\xa80W\xf1{\xbd\xaa\
+\xff\xb7*\xef\xad\xfd\x8b\x14\xf8\x1b\xa76\x84\xbb\x5c\xf4\
+\x09<\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x07\xe8\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x07\x9fIDATX\x85\xc5\x97ilT\xd7\
+\x15\xc7\xff\xf7\xde\xb7\xcc\xe6\x19\x0f\xb6\xd9q\x0dij\
+\x84\xa1,\x06\x5c5\xe4\x03(%%Q\x8bB\x15Z\
+\xe3\x80\x9a\x14\xe3\xa6\x8a\x14\xb5R\x94\x96\xb6R\xfb\xa1\
+\xf9P\xa9\xad\xd4FE\xa1,i\xb0I\x05\xa1\xca\xd2\
+\xaa\x09\x11qC\x09M\x00c\x96\x1a\x12\x88\xb1\x0dx\
+\xb7g\xb3g\xe6\xad\xf7\xf4\xc3\xcc\xd0\xe7\x05\xdb\xdfz\
+\xa4\xab7\xf7\xcd\xb9\xe7\xff;w\xce=\xef\x0d\xf0\x7f\
+66\x13\xa7\xd5O7.\xf3\xf9\xd5\xef(\x1c\x8f;\
+\x0eU8\xae[\xc49s\x18X\x96\x0b\xd6n\xbb\xf2\
+#\xd7u\xde1\x93\xbe3m\xc7\xb7[S\xc5Z[\
+\x7f\xe4[\x17\xfe\xb4\xf3\xc4\x8c\x00\xd6\xd5\x1f\xad\xd25\
+~0\x12\xf2-\xdd\xb0\xb2<T>\xafX\x84\x83\x1a\
+tM\x81#%L\xc3\xc6p\xca@\xcf`\xca\xbd|\
+s }\xb7?E\x8c\xd3A\xd3\xa2\xdf\xb7\x1e\xac\xeb\
+\x1a\x1f\xaf\xfa\x99\xc6\xcd\xe0x\xaf\xe5\xc0S\xf7t\xef\
+\x0b\xb0\xae\xbe\xe9\xbb~]\xf9\xc3\xf6\xafU\x05\x97.\
+.eD\x80$\x02I\x82K\x04)s~\x04\x803\
+@\xe1\x0c\xa9\x8c\x8d\xd6\x1b}\xf6\xe9\xf3\x9d\xb6Cn\
+\x93#\xb1\xb7e\xff\x8e!\x00X\xfb\xbd\xc6\x87\x14E\
+\x9c\xb4]70-\xc0\xba\x86\xa6m\x01]=\xf2\xfc\
+\xb7k\x02\xc5a\x1fd^\x90@ \x99\x03q$\x81\
+\x88 %A\x12 )\x07\xa2\xab\x1c\xae$|t\xe9\
+\xb6}\xba\xa5\xd3p\x80\x06&\xe9\xba\x10\xfc_[7\
+U\x85\xde8y\x05^\x00e\xbc\xf8\xfa]\x7f.\xe1\
+\x8c\x1fjx\xa2:0+\xe2\x83K\x04\xc3r\x10K\
+daX\x0e\x14!P\x14\xd4\x10\x09\xe9 \x02lW\
+\xc2r$H\x12lI0m\x17\x823<\xbc\xbaB\
+\xad\xac(S\xffr\xf2\xca\x81x2\x1bxb\xd32\
+Z87<!\xd9\x09\x00\x14P\xeb\xd7/\x9f\xaf\xcf\
+++\xc2\xd5\xf6\x01z\xff\xe3[r0\x91\xe1\x8c\x81\
+\x11Q\xce\x87H\x0a\xce\xec\xc5\xf3\xa3\xe6\xaa\xa5\xf3C\
+\x95\x15%\xdc\xa72\xa4M\x17\x19\xd3F2c!\x95\
+I!\xa8+\xd8\xb1eU k\xd8\xd0t\x95\xb9\x92\
+\xa6\x07\xd0\x05\xdfSS\xb5\xc0w\xe8\xed\x8b\xee\xad\xbb\
+\x098\x92\x84\xe0\xdc\xb4,\xa7\x9b\xb8\xbb\xa3e\xff\xae\
+O\x00\xa0z\xcf\xb1\xc8\xf5[C5\xed\xdd\xf1\xdd\x80\
+||M\xe5\x02\xb6\xa2r\x9e\x1f\x5c m\xb8p%\
+\x10O\xdb\x185]\xcc\x89\xe8h\xef\x1b\xc5\xb2\x85\x13\
+w`L\x0dT\xefy%\xe0W\x8a\x07WV\xce\xd1\
+.~\xda\x87h8\xa8(\x0aG\xdfP\x22.\x1c\xf5\
+\x8b\xff>\xb8=6Y\xcd\xacx\xb6)\xeaw\xf9\xb3\
+\x82\xb3\x9f|e\xe5\x22\xdf\xca\xcay\xca`\xd2D<\
+cCJB!\xf3\xb5\x0fD\xf1\xebW\xcfP\xcb\x81\
+:^X\xcb\xbd\x818\x15-&P\xe0\xd2g\xbd\xe2\
+\x0b\x0bJXI4\x18\x13B\xf4\x83X\xdb\xfd\xc4\x01\
+\xe0\xea\xbe\xba\xf8\xb9\xfd\xb5/\x19\x16U\x9e\xbd\xdcu\
+\xf2\xd57/\xa0$\xa4\xa0\xbc4\x00\xa2\x5c\xb1\x02\x00\
+\xe7\x0c\x9c\xc1\x1c\xa3\xe9\x9dH&\xb9a9\xd0|\xbe\
+\xe1\x90\xdf\xf7A@\xd7?p\x1c\xe7&\x91;\xe1L\
+Of\xad\x87j{\x00\xfaXQ\xb8\xa5(\x0a\xe2\xa3\
+\x16\x88\x00\x22\x801\x80\xb3\x89\x87nL\x0d(6\x5c\
+G\x00\xb3\xa3\xa1\x7f\x04\x03\xfa5\xc6y\xb28\x12\x0c\
+\x0c\x0d\x8f\xcc\xa8cV\xd77~?\xec\xd7\x7f\x5c\xfb\
+\xd8*\xad?i \x91\xb6 \x91\xeb\x15\x85\x1d\x98\x12\
+ \xeb\xc8.Up\x8cf\xb2\x1f\xf6w;\xed\xb6\x22\
+F\x93\xd9\x84\xe9\xba\xf6\xe8t\xe25\x0dM[}\xba\
+\xf6\xdb]\xdfX\xed\xd7u\x15A\x9f\x86\xf2\xd2\x00\xc0\
+\x00\x06\x06Up\x08\x06\x0fN\xce& \xad{\xfa\xf5\
+E\xe7\x0f\xd7\xde\x99I\xc6\x05[\xb3\xfb\xb5M\x0c\xfc\
+\xd4L\xfd\xa7\xec\x84\xd5\xbb\x1bIp\x96-\xcc%\x91\
+B\x04u\xb2@\xd2\xd5f\xb7\x1e\xde>8S\xe1'\
+\x9f<&\xba\x22F\xf9\xb9\x03\xbb:\x0a\xf7&\xf4\x01\
+\x00x\xf9\x85\xaf\xfb\x81\x5c\xf1H\x22\xd8\xae\x84\x94\xb9\
+B\xea\xe8Mb\xdf\x89\xf3)\xdbr\xab\xa7\x13on\
+\xee\xf0m\xdc\xb8\xd8(\xcc\xdb\x8b\xado2\xe2\x7f\x85\
+'q>\xd9BI\x84\xcb\x9d\x09\xb4v\xc4q\xb93\
+\x81kwR\x88\x8d\x9a\x18\x88gp\xe0\xcd\x96\xb4\xe3\
+:\x9b/\x1e\xdc\xf9\xf9\x94\xe9\x1217\xaa\x967\xb7\
+\x0d\xcc\xfd\x9f\x18\xb9@\xae\x89M\x090\xdef\x854\
+\xa8\x82\xe1\x8f\xc7\xceI\xc3v\xeb\x0a\xddp*\xfb\xc5\
+/\xc1\xae\xdf\xcd>, \xabN]\xed\x7f\xa0\xb9\xb9\
+YQUmk\xee[c\x1a\x00O\xa12\x06\x94\x14\
+i\xc8dm\x00\xc4\x05xm\xf5\x9e\xa3\xa5\xd3\x01t\
+F.\x85\xe3i\xf7\xab\x00_\xa6p\xaaj\x8b\xcf\xd9\
+H$w0\x06XB\x8cL\x09\xe0}f\x10\x017\
+{G 9\xc7\x0fw>\x84\x0dk\xca\xb7\x09\x8e\xf6\
+u\xf5G\x9e\xaf\xa9k\x9c\xd8\xdc\xf3\x96\x96\xf6\xa3\xaa\
+\xc2\xaa8\xa3e\x89\xb4\xb5\xf6\xf8\xe9\xebME\xe1\x22\
+\xc19\x1f\xb9\xba\xaf.^\xf0\x9b\xb4\x08%\x118c\
+\x98_\xeaGO,\x0b\xdb%\xc4F,\xc4F\x80%\
+\x8b\xca\xd4Y\xd1\x22\xf5\xf2\xa7\xbd/uv\x0f\xfdj\
+}\xc3\xd1\x13\xb6#_\xd3\x0c\x9c\xff\xa4\xe9\xa9T\xf5\
+\x8b\xefG\x8a\x84\xef\xb9\xd1\xb4\xf3\xe2\xf2\x8aP\xea\xed\
+\xb3w\xe6\x9e\xb9\xd4\xb1 \x10\xf0\x91/\xe0S\x13\x89\
+\xd4\x05\xaf\xd6\xa4\xc7\xf0w?\xda\x8c\xac\xe5\x22\x91\xca\
+\x22\x1c\xf2\xa3/\x91E\xf7p\x16\xfd\x09\x13\xae\x94\xd0\
+U\x81hP\x85\x94\x84\xae\x9e\x98{\xb7'\x96\x8e\x8f\
+dt!\x14\x9dq\x05\x94\x7fvK\xc7A \xa8\xbb\
+\xd1hX\xd1\x03~\xdc\xe9\xea\x19\xc9\x9a\xe6\x0f.\xbe\
+\xb2\xa3q\xca\x1d \x02n\xdeI\xc8\xc3o\xb5\xf0\x07\
+\xcbK\xb2[6<\xe8W\x04\x07c\x04\x02\x901\x1d\
+\xa4\x0d\x07B0D\xa3a1\xbb,\x12&\x09\x8c\xa4\
+\x0d\xa4\x0d\x1b\x8eK\xe0\x9c1\xaehpI*\x8e\x0b\
+\xa4\x92iX\x861j\xc6\xd5c^\xad\xf1\x00\x0c\x00\
+n\xf5&\xd0\xf8\xf7\xd6\x91\xe1\xdb\xe7\x1f\xb5S\x15\x9b\
+:zb?_\xb3t\xa1\xba\xa4b\xb6\x92L\xdb\x88\
+\xa5-\xd8\x0e\xc1r$L[B\x12\xdd[\xce5\x1d\
+B\xe6\xfa\x86\xe5\xba\x001d2Y\x0c\xf6\xf5g\x8d\
+D\xcf\xae\xb6\xe3/0\xe4jO\x02\x80\xf0\x88s\x00\
+\xfa\x82\xeam?\xbbr\xa3?=t\xfbR\xed\xadw\
+\x7fs\xab\xbf\xed\xbd\xcf\xccl\xf2o#|\xf6\xbc\xcf\
+o\xc7\x17\xfau\x05s\xa2!\xee\xd7\x150\x86\xfc{\
+!@2w\x95$\xf3y\xe4v+>\x9c\x94\x03\xbd\
+\x83\x99\xcc`{C\xdb\x89\xbdg\xf3:9\x07@\xb2\
+{\xe8\x80\x06@\xab\xde\xdd\x98\xca&n?s\xed\x8d\
+\xbd\xcd\x85{\x85QZ\xf5\xc8\x92\xf9\xcb\x1f\xab\xf5E\
+J\x1f)\x0e\x07eii\xd4\x1f\x0c\xf8\x98\xa6\xa9\x00\
+cp$`\x9a6\xd2\x86\x85d2\xed&c\x89\xac\
+mf\xfe\xd3}\xf1\xad\x9f\xf6_y\xa7\x03\x805n\
+\x98\xcc\x93\xbd\x06@\xffr\xed\xcb{\xae\xbc\xfe\xdcQ\
+\x00\xfa8\x80{s\xcd\x1f)*[\xbeee\xf1\xc2\
+\x155zQ\xd9\x97\xb8\xa6\x97\x81\xb8B\x8c\x09\x069\
+*\x1d\xb3\xd7\x88\xf7}8t\xe3\x9f\xef\x0e\x5c?\xd5\
+Y\x10\xf3\x0a\xe7\xaf\x86\xf7\x14L*\x96\x1f\xea\xb8\xcf\
+j\xbe~\x84w;\x018\x00l\xcf\x18\x9f\xf1\x98\xec\
+\x01\x98\xde\x22\xb41\xf6X\x16\x82\xba\xf9\xc0N~a\
+AX`l#\xa3\xbc\xaf\xd7\xdf\xf6\xac\xf3B\x19\xf9\
+\xeb\xa4\x7fL\x0a?\x87\x8a\xb1\xd9\x8e\x17.\x14S!\
+F\x01\xb8\x00=\x19HA\xf8^\xaf\x9d\xeeUK\xe4\
+\x85\x0b\x10\xdc\x03\xe0\x15/\x00x!\xbc\xc2N\xfe\xde\
+\x04\x9b\xd1\xbb\xde$k\xbc\xa3 \xee\x85\x98\xf8\x0f\xe4\
+>\xf6_\x84=\xc2\x88m2sv\x00\x00\x00\x00I\
+END\xaeB`\x82\
+\x00\x00\x07\x87\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x07>IDATX\x85\x9d\x97ilT\xd7\
+\x15\xc7\x7fo\x1bw\xc66\x06\xef@\x10I\xb0\x0dF\
+\xd8\xc6\xae\xb1\xc3\x14\x09\xc5U\x95\x0f\xa9\x1aAEi\
++5!J\xd5\xaa\x12k\x11\x04\x15U\x15\xadZ\xe1\
+\x88\x86\x90\x14\x1a)\xf9\x80\x95D\x09\xf9\x12U\xaaT\
+\xb58\xa8iTZ\x88\xa1j\x95Pd\xeca\x16\xdb\
+\xe3M\xf60\xdb\x9b7o\xeb\x87yo\xc6c\x8fk\
+\xb7W:z\xdb\x99\xfb\xfb\xdfs\xce]F`\x15\xed\
+\x97\xf0}\x01\xdeZ\x8d\xaf\xdb$\xf8\xcd\xcbpd%\
+?a%\x87W\xe0\x07U\xb5\xb5\xaf}\xf7\xd4)\xaf\
+\xec\xf3-u\xb0m\xb0,\xb0ml\xd3\x04\xcb\xc26\
+M~\xf7\xe6\x9b\xeaX(t\xf94\x9c\xfa\xbf\x05\x9c\
+\x87\x1f\xad\xab\xab\xfb\xf5\xb7\x8f\x1e\xf5\xa6\xc3a\xe2\x0f\
+\x1e,\x85\x03\xb6m\xe7\xee\x1d\xf364\xb0v\xfbv\
+~\x7f\xf5\xaa\x1a\x19\x1b\xbbx\x06\xce.\xc7\x90\x96\xfb\
+\xd0\x0f\x87k\xea\xeb/\x1c<v\xcc\x9b\x8eD\x88\x8f\
+\x8e\x82 \x14\x0c\xf2\xd7\xc5\xa30\x92I\x0cU\xa5\xfd\
+\xe9\xa7\x95\xc9@\xa0\xbb'\x91\xf0\x0c\xc2\x9fW-\xa0\
+\x1f\x8eV\xd7\xd7\xf7\x1f<v\xcc\x9b\x0a\x85H\x04\x02\
+ \x08\x08\xa2\x88\xb0\x08\xee\xb6R\x22\xccL\x86\xb6\xbd\
+{\x95h \xd0\xd3\x9bL\xda\x83\xf0\xe9\x8a\x02^\x81\
+\x13\xb5\x8d\x8d\xbf\xfa\xd6\xd1\xa3\xbed0H\xf2\xe1C\
+\x04\x07\xee\x8e^\x10\x84\x92\xb9\xcb\xbfsRc$\x93\
+\x98\x9aF\xdb\x9e=\xcaX \xe0\xf7\xa7R\x99A\xf8\
+\xdb\xb2\x02\xfa\xe1d]c\xe3/\x0e\x1c9\xe2K\x04\
+\x02\xa4\x82\xc1\x1cT\x14s\xb60\xfc%F\xed\x82\x0b\
+\x8f6z2\x89\xa9\xebt\xf8\xfdJ$\x10\xd8\xf3\x95\
+t:u\x1dn-\x11\xd0\x0f\xa7\x1a\xd6\xaf\xff\xf97\
+\x8f\x1c\xf1%FGI\x86B\xb9p;\x02\xcae\x99\
+\x8d\x1e\x0f\x09\xd3\xc4.\xa6\x14Ak\x14\x85:E\xe1\
+\x91a`\xdb66\x90M&\xb1L\x93\xf6\xde^%\
+\x1c\x08\xec\xf5\xab\xea\xdc \x0c\xe5\x05\x9c\x873\x8d\x1b\
+6\xfcl\xff\xe1\xc3\xbe\xf8\xf00\xa9p8\x1fj\x04\
+\x81rY\xa6\xb6\xa1\x81\xf4\xbe}\xac\x1b\x19!\x99\xcd\
+\x16D,\x10\xb0\xce\xe3\xa1\xbc\xa5\x05\xbd\xa7\x875\xe1\
+0q\xc7\xcf\x064G\xc4\x8e\xeen%\x1c\x08\xf4=\
+\xa5iS7\xe0\xae\x04\xd0\x07\x9f\xbet\xee\x9c\x12\xfb\
+\xfc\xf3\x1c\xdc\x0d\xb1\x03\xaf\xa9\xafg\xee\xecY\xf4-\
+[\xa0\xbe\x9e\xea{\xf7HjZn\xfa9\xad\xba\xac\
+\x8c\x8a\xe6fb\xa7O\x93mkC6M\xd6\x86\xc3\
+<\xd2\xb4\xbcO&\x99\x04\xdb\xa6k\xf7n\xe5\xce\x9d\
+;\xdf\xf8\x18\xce\x89y\x98,\x93\x0c\x85\x8aFd\xdb\
+6\x0d@\xfa\xd9g\xb1}>\x04A@\xdb\xb5\x8b\xcc\
+\x8b/\xb2\xb1\xb2\x12\xd1\x99\xf7\xd5^/\xe5\xad\xad\xc4\
+\xcf\x9cA\xf4x\x10E\x11\xf5\x99g\xa8\x04\xca$\x09\
+\x0b\xb0\x9c~\xe3SS\x88\x8a\x92g\xc8\x8b\xebhq\
+X\x83\xba\xcec\xd7\xaeAE\x05\xd9\xae.\x04A@\
+\xef\xedE\x90e6\x0e\x0c\x90J\xa7)om%y\
+\xfcxn\xa6\xd86b:M\xd5\x85\x0b\x04\xb3Y\xd2\
+\x0bj\xc1v\x06\x85e-#\xc0q\x5c8\x9dLA\
+ \x12\x8b\xb1\xe9\xddw\x11e\x99\xec\xce\x9d\x08\x82\x80\
+\xb1k\x17\xa2$Qy\xe3\x06\x89\x13'@\x14\x11\x01\
+!\x95\xa2\xf2\xe2E&B!\xe2\xaaZ\x04\xce\x0b)\
+)\xc0]J\x9d\x1f`\xdb\xf9E\xc72M\x22\xd1(\
+\x9b\xde\x7f\x1fQQ\xd0\xdb\xdbs\x22\xba\xbb1\xba\xbb\
+\xc9\xe71\x95\xa2\xfc\xf5\xd7\x99\x18\x1d%\x9eH\x14\xa0\
+\xffK\x04\xb0\xed\xfc|_(\xc4\xb2m\x22\xc1 \x8f\
+]\xbb\x86(\x08\xe8\x1d\x1d\x85U\x11@U\xf1]\xb9\
+\xc2\xf8\xfd\xfb\xc4c\xb1\x22\xb8\xa5\xebX\x86\x81\xe5\xa6\
+#\x9b-\x1d\x01\xdb\xb2\x8a;u\xc4\xb8\xd5nZ\x16\
+\xe9x\x9c\xca\x9b71;;\x8b|\xa5\xc9I\xa4L\
+\x86\xe4\xfc<\xd9\x99\x19\x8cx\x1c3\x95\xc2PUl\
+\xcb*D\x000\xea\xea\x96OA\xbe\x06\xdcH\xb8\x05\
+)\x08To\xdeLEO\x0f\x99\xe7\x9fG\x14E\xe7\
+\xb5\x93\xa6\xa6&\xd4\xe7\x9e\xe3\x89p\x98\x7f\x0d\x0d\xa1\
+\xebz\x11\xd45\x0b\x8aR\x90O\x9f\xed\xec\xe9XV\
+N\xb1\x13\x11\xf7}\xf5\x93OR\xe5\xf7\x93}\xe1\x05\
+\x04YF\x14E$]G\x9a\x9ar\xf4\xdb\x98\x9d\x9d\
+\xd8\x87\x0f\xd3~\xf0 \x92\xc7\xb3\x04l\xb9\xcf%k\
+\xc0\xb2r\xe6\x86\xd5\x8d\x00P\xdd\xdcL\xd5\x9e=h\
+\x07\x0e H\x12\xa2(\x22f\xb3Ho\xbc\x8141\
+\x81~\xe8\x10f{;\x96eauu!\xca2\x1d\
+>\x1f\xff\x18\x18@\xd7\xb4\x02\x98\x05\x85XJ\x80m\
+\xdbK\xc2_\xdb\xd6F\xa5\xdfOf\xff\xfe\x02\x5c\xd3\
+\x10/]b\xe4\xbd\xf7\x88G\x22\xb4)\x0a\xb2\xa2`\
+\xb4\xb6\xe6j\xa5\xad\x0d\xf1\xd0!\xba**\xf8\xec\xf2\
+eLM+\x12P2\x05\x96iR\xbea\xc3\x92\xf0\
+W<\xfe8\xe6\xe6\xcd\xe0\x9e\x05T\x15\xf1\xca\x15F\
+?\xf8\x80\xb9\x91\x11tM\xe3\x9fo\xbf\x8d\xf1\xe1\x87\
+H\xc3\xc3\xb9\xadZ\x10\xb0\xd6\xaf\xc7\xd7\xd8\x88\xb2f\
+M\x11\x5c\x10\xc5\xa2\x14H\x00_\x83\x9a\xe9`\xb0c\
+G_\x9fl\xa6\xd3\xe8\xf1x\xbe \xe7\x87\x87\xa9\xf4\
+x\x90e\x19\xb3\xba\x1a\xcf\xc0\x00\xa3\xef\xbc\xc3\xdc\xfd\
+\xfb\xf9NM\xc3`\xea\xee]\xeakk\x11\x1b\x1bA\
+\x92\xf0|\xf4\x11C\xaf\xbeJrr\xb2\x08\xfeDS\
+\x13s\x89\x84=\x1a\x8b\x09\x1f\xc39\x09`\x10\xfe\xd0\
+;?_?=>\xde\xb1\xa3\xb7W6fgQ\xc7\
+\xc6\xd0gg\xd1\x22\x11f>\xf9\x84\xb5UUT\x8d\
+\x8d1|\xf5*s_|\xb1\xa4\xba\x0d\xc3`\xf2\xf6\
+m6l\xda\x847\x14b\xe8\xfcy\x12\xe3\xe3\xf9\xef\
+\xa2$\xb1\xa5\xa5\x85X&c\xdd\x08\x87c\x22|y\
+\x10f\x8b\xce\x14\xfdp\xb1\xa1\xa6\xe6\x87_\xef\xeb\xf3\
+N\xdf\xbeM\x22\x14*\xe4M\x92\xf0\xd4\xd4\xa0NO\
+/\xa9\xee\x85\xcf\x92\xd7\x8bXVF&\x16+\xc0e\
+\x99-\xcd\xcd\xcc\xa4R\xd6_\xc2\xe1\x99,\xec\xfe)\
+<\xcc\xa7\xc0m\x83\xf0\xc7\xdd\xaaZ=\x11\x8d\xee\xec\
+\xf0\xfb\x15#\x95B{\xf4(_\xb9z*\xf5_\xe1\
+n:\x8cL\xa6 H\x96\xd9\xd2\xd2B4\x910\xff\
+\x1a\x89L\x00\xbd?\x81\xfc\x9e\xbf\xe4L8\x08\x7f\xf2\
+g2k\xc7\xa2\xd1\xce\xce\xde^\xc5L\xa7\xc9\xc4\xe3\
++\x82K\x99\xac(4m\xddJ8\x163\xfe>>\
+\xfeP\x84\xa7NCt!\xaf\xe4\xa9\xf8:\x5c\xdf\x95\
+\xc9TG''w\xee\xec\xeeV\x0cU\xcd\x8bX-\
+\x5c\xf1xh\xda\xb6\x8d\x91\xd9Y\xe3\xb3h\xf4\xc1\x1d\
+\xf8\xeak0\xb9\x98\xb5\xdc\xff\x82\xf2\x1b0\xb4]\xd3\
+\xd6\xcdLN\xee\xe8\xec\xea\x92\xf5L\x065\x91X\x1d\
+\xbc\xac\x8c\xa6m\xdb\xf8\xf7\xf4\xb4qkjj\xf8-\
+\xf8\xdeMH\x93[\xe5\xb3\x0bA\xcb\x9d\xae+];\
+\x09/o\xad\xa8\xf8\xce\xbe\xbd{\xcb\x04wi\xb6,\
+,w\xe5\x5c`\xee;\xdb\xb2\xb8\x13\x0c\xea\xb7gf\
+\xee\xfd\x16\x8e\xcf\xc2,\x90\x00\xe2@\xcc\xd1\xb9\xac\x00\
+72k\x80\x0a\xa0\xe2\xc7p\xb2\x16^Z\xc6\xb7d\
+K\xc3\xadKp*\x01\xf3@\x8a\xdc5\x8e\xb3\x1f\xad\
+$`\xa1\x90/\xb9B\x9c{\x8fc\x0a\xb9\x95\xd4\xed\
+\xc3\x02\x0cr!\xce\xe64\x90\x00\x92@f1x\xb5\
+\x02J\xf9\x8b\x8e0\x17.P<AL\x0a\xb5\xbab\
+\xfb\x0fC+\x09\xef\xbdQ\xf6l\x00\x00\x00\x00IE\
+ND\xaeB`\x82\
+\x00\x00\x04\xb0\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x04gIDATX\x85\xed\x96mh\xd5U\
+\x1c\xc7?\xe7\x9c\xff\x7f\xbb\xce\xcc\x8a\x1c\xa4\x22\xc3\xb4\
+\x15\x11\x14Y\xb3\xdd\xa9eV\x9b\xd9\x83J\x0b\x02S\
+\x12M{\x91\xd8\xe6^\xc6 \xa4^\xa8AA\xd0\x13\
+\xcc7EM\xca0\xf0EJ\x96\xe8E\x03\x89\x05\xea\
+\x16e\xea|\xdau\xde=\xde\xbb{\xefy\xe8\xc5\xff\
+\xbf\xdb\xbd\xd7\xad\xf6@\xef\xf6\x85\x1f\xbf?\xe7\xcf\xf9\
+\xfd\xbe\xe7\xf7t\x0eLa\x0a\x93\xc4\xe2FU_\xdd\
+\xe0\xb9\x89\xee\x97\x93\xf0-\xa2;\xbc\x9d\x0b\xee\xba\x7f\
+\xaf\xef\x95N\xd8\xc8\x84\x08<\xdd\xc8\xf4\x9a&\xef\xc0\
+\xe2\xfbV\xbc\xf5\xfa\x0boG<\xe5O\x98\x807\xde\
+\x0dUM\xccM\xe2\x1fZ\xb3t\xd3\xfcE\xf7.\xf3\
+\x95T\x13v\x0e\xe3\x8c@u\xa3\xf7\xd8t\xef\xd6\xb6\
+7V\xbf\xb3\xf0\xe1\xca%~{\xd7I\xac\xd3\x93\x22\
+0\xe6\x08D\x1b\xd4\xfaY3g\x7f\xb4\xf5\xc5\xe62\
+\xe5+:\xbaN\x921i\x1c \xc0\xfc\x7f\x04\x9a\x91\
+\xd1\xa4\xb7\xbbr\xceC[6\xd4\xed\x88\xf4e\xe2\x9c\
+\xef\xfe\x1dc5\xceYp\x0e\x07j,\x9dp|\xb7\
+\x16\xe3\x22\x10mb\x06\x83\xde\xfe\x9a\x07\xea\xaa\x9f\x8b\
+n\x88\x5c\xeai'\x91\xba\x8au\x06k\x0d\xc6i\xa4\
+\x904\xbf\xf6\x09\xd6Y\x1c\x06\xebl(\x06\x17jc\
+\x0d\xef\xb6\xbc94\xae\x08\xd4l\x8f\xccw\xc2\x1c^\
+\xbbl\xe3\x9cE\x95O\xf8\x1d]'Hf\xfap\xb8\
+\x9ca\xeb\x0c\xb1\xbf\xbe\xc5X\x8d\xb1\x1a\x1djc\xb3\
+h\x9b\xcd\xe9\xe5\xf7\xbc:\xea!G$P\xd3\xe8=\
+^\xe2\xfb\xdf\xad\xab\xdb1c\xf6\x9d\xf3D\xdb\xe5\x1f\
+\xc9\x9at\xf8\xd7\xe1\xb0 \x1c\x16\x83\xc5\xe0\xa4\xc6\xa2\
+q.\xd0\xc3\xe4\xac38,\xce\x8d\x9e\x9d\x9b\x08D\
+\x1b\xfc-w\xcc,\xdf\xb3\xaev\xfb4\xe9C\xdb\xe5\
+#A\xae\x85@\x08\x10\x12\x10\x00\x16+\x0c\x16\x1b\x90\
+\x10\x1a'\x0d\xe4\xa5'\x08\xbf\x06,\x0eF\xec\xd7\x02\
+\x02\xd1&\xff\xab\x8a\xf2\xca\xe7\xd7\xafl\x8c\x5c\xea;\
+Kg\xf7Y\x04\x02!\x04R\x06Z\x00B\x10\x9c\x0c\
+K\xca\xf4\x93\xcc\xf42\xa4\x93h\x93\xc6\x1a\x87p\x0a\
+\xe9|\x14%amXp\x8c8\xad\x0a\x08Xk\xab\
+\x85\x07\x83\x99\x04}C\xd7\xd1&\x03\x04\xce\x95\x10\x01\
+\x19\x1c\x83\x99\x1e\xfa3q\xfa\xb3\xd7\xc9\xd8T\xd0\x09\
+\x0e\x9c\x05k\x1dV;\xacq\x18\xed\xf0\x88\x90\xd6\xa9\
+\xb1\xa5`\xeeySq\x8e\xb3\xbb>\xde\xbfs\xf3\xb3\
+K_*S\xd2\xe7\xcc\xb5\x18\xa5%>\xd2\x03C\x1a\
+-\xd2 \x0dR\x09\x84\x14xR\xe0\x10\xe0\x1c\xd6\x82\
+0a\x86\x00\xe7 \x9bM\xe5\x13P\x14\xcd\x8c\x82I\
+\xd8\xda\x8a9\xb6Ko\xef\xedKl\xdd\xf7\xc3\xde\xa4\
+o\xca\xdc\xa3\x15u\xa4\xed \x03\xd9n2\x0c\x22\x94\
+Ey\x12\xe5\x89@|\x81\xe7\x8b\x7f\xd6\x94@z\x02\
+\xa9\x04R\x81T\x05\xad\xefST\x0b#\x8db\x19\xdb\
+c\xbe\xee\xbf\x92\xa9\xfd\xfe\xa7}\x89+W\xaf\xea'\
+\x17\xae\xe7\xb6\xb2Y\xa1\xd1\xc0\x81\xf2$\xca\x17(_\
+\xa2|\x89\xf4C\xc79\xe7A\xea\x82\xda)\x88xA\
+\xd4GjC\x09\xc8S-\xbam\xde\x12\x96\x1c\xb3\x87\
+[o$\xba\xee^Z\xf5J\xe9o\xdd\x07\x89\xa7\xff\
+D\xe59YY\xd1\x00\x8e\xb0\x06\x824\x04\xdaau\
+X\x13\xc6\x15\xd8\x0e\xc5\x8eF \x87\x0bG\x89\xf7v\
+\xe8U\xba\xbemOO_\xa2v\xd5Sk#\x9dC\
+\xbfr~\xf0\x97\x80\x84\x17\xd4\xc1{\x1f4\xff\x9b\x99\
+|\x8ci\x14\xbb\x90\x9d\x03l\xef5\x86N|h\xb6\
+e7vn\xfeb\xa0\xa5a\xcd\xaa\xfa\xc8\xed\xe5\xe5\
+\x9c\xee?\x84P&\x97\xe3\xe3\xbbu\x14\xd0\xa1d\xf3\
+$\x93\xf7m\xf3\xec\xe7BR\x0cS$\x1a\xd0\xa7>\
+\xd7\x9fv\x9e\xee\xd9\xf4\xe57-\x03\x83q\xe7\xaa\xca\
+\xeb\x99\xe6\xdf\x12\x0c\xa6\x00I`\x08H\x179\xd5y\
+b\xc2\xb5\x1cF{M\xb8\x11\xbe]\xfc\x8c\xbb \x15\
+\x07/\xa7\xce\xac\x88\xa8\xb2\xb2G\x16<\xa3JT)\
+GcG\xb8\x18\xb3\xef\x17\x9d\xb8\x98D6$W\xd0\
+\x86\xa3\xd5\x80\x0b\x0d\xd8<\xd1\x80w\xeeg\xdd>p\
+\x89\xe5\xce\x1e\xfd\xac\xab;^\xb5\xba\xb6>\x92\x17\x81\
+\xe1\xf0\x16Gp\x98\xd0M\x97\xc2\x7f\xbd\xa7\x86\x1d\x9b\
+p\xb3\x01t2A\xeab\xcc\xb6\x96.\xb81\xbd\xe3\
+\x8f\xf6\x07\x93C\x03\xde\xc5\x98\xddYt\xf2t\x9eL\
+\xee\xd9T\x04\x11\x12\xf7\x00\x7f\xf16\xf5r\xf8\x18\xf1\
+\xc3\xb5\xdcu5\x85)\x8c\x05\x7f\x03\x8dcF\xa6\x8c\
+\x98\x19\x1a\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x06\x92\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x06IIDATX\x85\xb5\xd5}l]u\
+\x1d\xc7\xf1\xf79\xf7\xdc\xc7\xf6\xde\xdbvm\xd7\xadv\
+\xed\xba\x95\x87\xb5\x85\xd9\xb1\xb1\x8d=\xb0\x11\x10\xcc\x88\
+\x89\x93\xb1dVS6j\xe2\x031\xe8bD\xd1\x09\
+F\x0d\xa0\x18\xd82\x15\x12\xca@c\xda1\x13\x94\x02\
+\xdbD]]\xd9D\xd9CW\xba\xa7\x92>\xdc\xc7\xde\
+\xb6\xf7\xe1\xdc\xc7\xf3\xe8\x1f\xde\xe2\x9d\x94tv\xeb7\
+\xf9\xe6\xe4wrr\xce\xeb\xf79\xbfs~\x02\x05\xf5\
+\xdc}\xd8)\xb3\xbcd\xc2}\x98h\x02f7\xaa\xf1\
+\xedG\xba\x880G%\x14\x0e\x9e\xdf!\xfd\xa4\xe6\x86\
+[\x1em\xb9\xe7k\xf6\x5c&\x81o\xe0\x98v\xf6\xf8\
+\xeb#\xba\xaa7=\xdaEf.\x00\xd2\x95\x1c\xf3K\
+\xb7\xdc\xd9f?r\xe0\xbb(\xaaFIE\x8d\xb4\xb0\
+\xbeqa\xe0\xc3\xfeV\xd0\x7f3\x17\x00\xb1p`\x18\
+\xa6W\x8e\x06\xb1\xb8\xcax\xf0\xa9\x8b\xc8\xd11\xea\x1b\
+\xd7;\x04\x81\x1f\xee\xd9s\xe5\xb5s\x02\x10\x11\xdeM\
+\xc7|\xa6\xa1\xa49\xf8X3\x129*k\x9b\xf0\x94\
+Vz=\x17-\xf7\xcf\x05\xc0R8\xb8\xb7\xd9\xfc \
+4|\xa1\xf5\xee\x1d\x8f[]F\x04\xb7\x11\x04\x0c\x8a\
+\xbd\x95\xb6\xb0\xff\xf2\xd27\xcf\x9a/\xcc)\xe0\xad>\
+\x82\x9fi2\x84\x09\xff\xf9\xd5M\x1b[\xad\xa9P\x1f\
+\xd9\xe8\x10\xa5\x8b\x963|\xf9\x83\xd2\xbbn\xd6\xba\x0f\
+\x9f#t=\x01\x1f{\xaf\xd1\x06\xe3\xa7\x93\xa1\x91s\
+\x17N\x1e\xd2J\x16\xafCS2\xc8\xc1\x01\x16TU\
+\xd9\x9c\x0e\xe1\x07\xef\xfe\xd6\xfe\xfc\xdf;0;;\xaf\
+\xc4\xcf\xb6\x84\xe9N\xfe|;5\x16\x8b\xa5\xbf\xe5\xf6\
+\x8dn33N:\x1e\xc2V!b+\x99`A\xc3\
+z%2\xf2\x0f\x8b\x91N.\xb9c\x17\xc3s\x02\x00\
+\xf8\xe5\x83\x96\x07\xac\x0e\xebK7\xdf\xba\xb0\xc8^\x1a\
+\xc4=\xaf\x86\xfa\x15\xad@\x19\xaf\xb4\x7f_\xb9p8\
+\x96PU\xb3}\x1f\xfcaN\x00\xbd\xaf\xb04#\x0b\
+\xc7\x8b\xbc\xde\xca\x86\x95_\xc0[\xb1\x8c\x13\xaf\xf6\xf0\
+\xc6\x9e\xa3\xd49\x8bX`\x88\x1c\x09\x04d\x0dj\xf7\
+Ct\xb6\x00i\xba\x93=\x1d|\xcb0\xc4g\x16\xde\
+t\xa3^\xb9\xe86\x82}a\xf6\xef~\x0d)a\xd2\
+\xde\xb2\x1a\xaf(\xe2\xf3\xf9p\x84\xc3bZ\xd7[\x80\
+?\xcf\x160m\x02\xc7\x0e\xb0Q48\x22\x08\xd5\xc6\
+\xfb\x1d\x9a}\xe4\xc48k\x1cnV\xd6/\xa1\xb4\xaa\
+\x8a\xf7\x02~\xfe8\xd0G\xcdm\xa6\xbe\xeaa\x06L\
+\xc3\xf8\xce\xba6\xba\xaf\x1b\x00\xe0\xc4\xabx\xba\xda\x09\
+\xd5\xe7p.\xd3\xc1SR\x82\xec\xf5\xf2\xb7D\x04\xeb\
+|\xc1\xdc\xb6\xaf]X\xbc\xa2\x8eXd\x90\xe1s\xaf\
+\xa7\xb3\xf2\xd0A\xaf\x83\x87\x1b\xb7\xa1\x5c\x17\x00\xc0#\
+0\xb4\x0ej\xdd\xc0Y\x87\xc8\xb8\x0d\xb6\xfcx\x0b\xeb\
+w\xb6c\xe6z!\xf7>\x8anE1\x1a\x09\x0e\xf6\
+f'}\xc7\xce\xe44\xee\xdd\xd4F\xec\xba\x00\xbe\x02\
+\x9f\xb5\x0bt\x09V\x9c\x9f\xdeZ\xcc\xf6g\x9f\x10l\
+v\x15\xd3\x88\xa3e\xc7\xb0\x08\xe3\xe8\xba\x1f9\x91\xc2\
+\xb0\xad!\x19O\xab\xa1\x81\xceQ\x0c}\xc3\xda/\xe3\
+\xbff@\x1e\xd1\xb0\xf5\x17<\xd1\xb8\xe9\xfe\xed\xf3\xab\
+\xab@\x0b\xa3SJ&\x95\xc4\xeeP\xd1\xb3~\x14-\
+A,\x1a\xc5\xe6^\x87(.1\x86\xcf\xfcj\xc2\xcc\
+\xa56\xacm\xe3\xfcL\xf7\x9fq\x87\xfb5\x5c*\x9a\
+GR3T\x145\x05F\x0c%\x1d\xc4n\x13H\x8c\
+\x87A\x90\xd0r\x06v\x87\x97X\xa0\x1bM\x1b\x10\xeb\
+W=V.9\xcbO\xf6\x1c\xe0\xf6k\x06\x00`\xe0\
+W3qCUA\xd3dtu\x12AHbh9\
+\xb2\xa94JV\xc7\xd43\x14y\xeb\x99\x1cy\x9bl\
+\xa2G\xa8_\xfd\xb8\xa7\xc8\xdd\xf0\xce\xf1\x97\xf9\xe25\
+\x03\x0c\x81\x0f\xd3\x89HV\xd3\xec\xa4\x92\x09\x102\xa4\
+\x13AJ\xca\x8b\x89\x04\x82\xb8<^\x921\x0d\x84\x08\
+\xee\x8af&\x86\x8f\x12\x0fuR\xb7r\xb7\xab\xa4z\
+\xf3\x0b\xbd\x07x\xb1\xb7\x13\xe7\xac\x01\x02\xfc+#\x87\
+uC\xf0\x90\x92\x13\x18\x86B\x22:\x8eER(*\
+q\x13\x1e\xf5\xe1\xf2T#GR\x98\xda(e5k\
+\x89\xfa\xfe\xc9\xd8\xe0~\x16\xde\xf4\x80\xa3\xfa\xd6o\xb6\
+\x8aZ\xf1\xc8\xf1\x0e\xb6\xce\x0ap\xc7\x10\x03ZN\x16\
+45EN\xb5\xa1\xe4\x92(J\x8ed<JiU\
+\x1d\x89\x09\x19\x8b\x94C\x94\xaaI\xc7&1\xf4Q*\
+\x97\xdeIj|\x90\x91\xd3?\xc3]\xd6`\xbba\xfd\
+s\xe5\xa2\xcd{\xb0\xa7\x83\xf6\xc2{_\xd5\x96\xfa\xa3\
+\xbfb\xee\xfc<-6\x97g\x99(\x15\xa1\xa4G\x11\
+D\x89\xb4\x9c\xc6\xe9.\xc6\xeeZDx\xa8\x9f\x8a\xba\
+f\xa2\xfe\x08\x82e\x1c\xab\xc3\x8bg\xferL\xcdJ\
+\xe0\xc2!R\xe3g\x94dbt\xefW\x9f\xe1\xe9H\
+\x04\xe3\xffJ\x00\x90bq^\x8e\x05\xfbe\xa7\xbb\x89\
+\x94\xacb\xb5\xdb\x88Od\xc8$\xfc\x14\x97\xd7\x83X\
+L*z\x91y\x8bW!\x8f\x09\xa8\x990.\xcf\x1a\
+\x8a+\x96\xa3g\xe3\xb9\xa0\xaf\xbfc\xd3C<\xd9\xdf\
+\x8f\x13p\x026@\xb8\x9a\x04\xac\x80\xad\xbb\x17\xff\xb6\
+\xcd\xe9]\x9e\xca\x06\xb7axQ\x95\xcbX\x1du\xc8\
+\x13\x01\x1c\x1e\x0fNO-\xa1\xc1\xd3\x94U7\xe0\xf0\
+4\x13\x1d\xbdL*~^\x09]z3\xfe\x97\x93\xfa\
+C;\xf7\xd0\x99\x9f\xb0P\xd83\x01,y\x805\x9b\
+E\xbag5\xb2\xc3\x12\xddPQ\xbb\xc5\x9a\x18;\x85\
+\xbb\xa2\x96X \x84(%q\x95.\xc7\xc4\x81\x92\xd2\
+\xb19K\x8cx\xe8\xb4\x16\x0aFz\xbf\xf1\x14\xbb\xba\
+\x0e\xe3+xha\x993\xfd\x09\xa5|TV\xc0\xe6\
+tb{k/\xef|\xaaq\xf3R\xc9\xbeB4\xf5\
+SH\xd6\x1aF\xfb\xbb\xa8k\xf9:\xe9\xf8\xa0\x1e\x1a\
+8$N\xc8\xfa{\xbf\xfb\x13{\x7f\xff6\x97\x00\x15\
+P>\xe98\x13\xe0\xa3\x04\xa6 \xbb\xdb\xb8\xf1s\x1b\
+\x85\xee\xea\xc6\x1d\xael2\x85\xd5\x966&}\xa7\xd4\
+l:B`\xcc|\xa3\xeb(\x07_;\xc20\xa0\x03\
+Z\xbe\xd5\xff\xe9)\x80:\xe3^\x90O\xc1Z\xd0\xd2\
+\xbe\xef\xb1\xb9y\x09O\xea&F<\xc1\x89\xd3\x179\
+\xfc\xf4\x8b\xf4\xa5T\xcc\xa9h\xf3\xadO\x03)\x04\xe5\
+\xae\x06\x00\xffyw\x1f\x01\xf2m)h\x91\xff.\xb0\
+B\x80\x91\xef)\xc4\xd4Q\xc9\xf7\x8ck`\xba\xb2\x14\
+ \xc4\xfcX\xe0\xe3\x9ft!bj\xd6S\x89\x5c1\
+\xb3k\xad\xc2\xd5]\x98@!\xe2\x13\xeb\xdf4\xc1\xdb\
+\x049\x93)\x01\x00\x00\x00\x00IEND\xaeB`\
+\x82\
+\x00\x00\x17\xe1\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x90\x00\x00\x00\x90\x08\x06\x00\x00\x00\xe7F\xe2\xb8\
+\x00\x00\x00\x19tEXtSoftware\
+\x00Adobe ImageRead\
+yq\xc9e<\x00\x00\x03(iTXtXML\
+:com.adobe.xmp\x00\x00\
+\x00\x00\x00<?xpacket beg\
+in=\x22\xef\xbb\xbf\x22 id=\x22W5M\
+0MpCehiHzreSzNTc\
+zkc9d\x22?> <x:xmpm\
+eta xmlns:x=\x22ado\
+be:ns:meta/\x22 x:x\
+mptk=\x22Adobe XMP \
+Core 5.6-c067 79\
+.157747, 2015/03\
+/30-23:40:42 \
+ \x22> <rdf:RDF \
+xmlns:rdf=\x22http:\
+//www.w3.org/199\
+9/02/22-rdf-synt\
+ax-ns#\x22> <rdf:De\
+scription rdf:ab\
+out=\x22\x22 xmlns:xmp\
+=\x22http://ns.adob\
+e.com/xap/1.0/\x22 \
+xmlns:xmpMM=\x22htt\
+p://ns.adobe.com\
+/xap/1.0/mm/\x22 xm\
+lns:stRef=\x22http:\
+//ns.adobe.com/x\
+ap/1.0/sType/Res\
+ourceRef#\x22 xmp:C\
+reatorTool=\x22Adob\
+e Photoshop CC 2\
+015 (Macintosh)\x22\
+ xmpMM:InstanceI\
+D=\x22xmp.iid:A7137\
+DD0390811E5A111E\
+32F6F5F6555\x22 xmp\
+MM:DocumentID=\x22x\
+mp.did:A7137DD13\
+90811E5A111E32F6\
+F5F6555\x22> <xmpMM\
+:DerivedFrom stR\
+ef:instanceID=\x22x\
+mp.iid:A7137DCE3\
+90811E5A111E32F6\
+F5F6555\x22 stRef:d\
+ocumentID=\x22xmp.d\
+id:A7137DCF39081\
+1E5A111E32F6F5F6\
+555\x22/> </rdf:Des\
+cription> </rdf:\
+RDF> </x:xmpmeta\
+> <?xpacket end=\
+\x22r\x22?>\x03\xa0\x95\x84\x00\x00\x14OIDA\
+Tx\xda\xec\x9d\x09t\x94U\x96\x80o\xedK\x92J\
+\x08\xa9$$\x10@$\xec\x10\x10\x05Yd\xd8\x0d\x10\
+\x04\xba\xc7qN\xdb2n\xa0c\x9f\x86\xc1n4c\
+K\xd3\xea4HK+Gi\x15\xa1\x15\xd43\xda\xa7\
+\x15\x9aEF\x86\x0ez@\x16A\xc0\x80\xec\x8b$$\
+d_\xaaR{\xfdU\xf3nQa\xd2\xe1\xff+U\
+\x95Z\xde\xab\xbc\xcby\xfc\x81,\xff}\xef}\xb9\xf7\
+\xbe\xed>\x99\xd7\xeb\x05.\x5c\xc2\x15%\xfe%\x93\xc9\
+\xbad\xe5W\xee\x9f\x22\xabm\x19\x93/x4Ce\
+2!O\x0eB\x8eL\xe6\xce&\x9f\xd2`\x91\xcb\x9d\
+\xf8\x04\x8fG\xed \x0f_\xf1z\x95U\x1ePTz\
+\xbd\x8a2\x85\xdcq\xda\x98|\xe4\xc2\xca\x89%]\xf2\
+\xb7\x10\x8d\x8f\xcc\xf7W\x82\x03\xb4\xe4\xcb%z\x87\xbb\
+\xdb\x1c\xd2\xe1\x93\x95r\xcbH\xa5\xc2\xd2W\xadhN\
+U+L\x1a\x02L'\x1bQ\x09N\xc1\xe0p\x0a\xa9\
+\xcdn!\xe9\xaa\xdb\x93t\x82\x00\xb9O\xa3l\xdc\xb9\
+n\xe6:+\x07\x88Ayfwq&xeO+\
+\x14\xd6B\xad\xb2n\x90NUm \x16&\xc6\x8d\xab\
+\x00\x9b+\xcbdwg\x9c\x15\x04\xfdn\xd2\xd2o\xaf\
+/\x5cU\xc3\x01\xa2T\xfe\xfd\x8b\xdfLV\xc8\xed\xcf\
+j\x95\xb5\xe3\xf4\xea\x8an2\x99\x87\xb2\xc6\x96\x83\xd5\
+\x99\xdbhw\x1b\x0f\x0a\x1e\xed\xda?\xcdze\x1f\x07\
+(\xce\xf2\xd4\xae\x97\xeeU)L+\x93\xd4\xe5\x13\xb5\
+\xaaZ\x1dK\xba\xdb]F\x9b\xc5\xd9k\xbfK0\xac\
+|g\xf6\x8aC\x1c\xa0\xd8\xb9\xa7$\x8fW\xf5r\x92\
+\xba\xec\x91dMYw\xf6\x1d\x81\x0cZ\x1c\xbd\xea-\
+\xce\xbc-r\x99\xebE\xe2\xe6,\x1c\xa0(\xc8\x13;\
+\xd6\xe4\xebT\xb5\xebS\xb5\xe7\xa6\x90 X\x9e\x88A\
+)\x09\xc2=\xcd\xf6\x81%6\x97\xf1\x99\x8dE\xcb/\
+p\x80\x22 \x8bw\xbd4Z\xa7\xac\xddd\xd0]\x18\
+N~C\xbb\xc4\xf0\x98XX0\xd9\xf2Kmn\xe3\
+\xe3\xef\xce^q\x8c\x03\x14\x86,\xda\xb9j\xb8VU\
+\xbd9Mw\xb6\xa0\xab\x80#\x06R\x93m\xd0I\xbb\
++k\xe1\x869\xc5\xa5\x1c\xa0 \xe4\xf1\xed\x7fL\xd7\
+\xab+\xff\xd2M\x7fj\x0a\x01\xa7k\xcep\xde\x0e\x92\
+\xb7\xd1:\xac\xc4\xea\xccyp\xd3\xdce\x0d\x1c \xe9\
+\xa1\xf8ki\xba3K\xd4\xca&%\xc7\xe6vq\xba\
+\xd3\xdcM\xb6\xc1\xeb\xfe4\xeb\x95_q\x80\xda\xc8\x93\
+;V\x8f5h/oK\xd1^\xce\xe2\x98t,f\
+{\xbfj\x93\xbd\xdf\xbc\xf7\x8a\x9e?\xdc\xa5\x01\xf2\xad\
+E\x99\xc7~\x9c\x9et\xe2!\x85\xdc\xc1\xddU\x08\x22\
+x4\xde\x06\xcb\xc8O\x8c)\x87\x7f\x16\xaf\xb5\xb8\xb8\
+\x02\xf4\xc4\xf6\xb5CRu\xe7J\x88\xd5\xc9\xe48t\
+\xce\x1a5\xdb\x06N\xdd8\xf7\xd9\x1f\xe2\x01P\x5c\xe6\
+S\x9e\xda\xf5\xbb_g\x19\xbe.\xe5\xf0t^\xd0\xed\
+c[b\x9b\xc6\xe3\xfd1\xb5@d\x84%\xd7\xa9\xaa\
+vg$\x7f7\x83\xf0\xcb{?\xb2]\x09u-w\
+\xed\xb1\xb9\xb2\x0b\xc9H-&\x0b\x811ua\x04\x9e\
+L\xe2\xb2N\x1a\xb4\x97z\xf0\xce\x8e\x9e\x98\xecw\xde\
+ e\xc4\xc6\xa2\xe5\xb5\x09\x03\x10\x19e\x15\xa4'\x95\
+\x1e$\xd6G\xc7\xbb8\xfaB\xac\x90\xb5\xde2r\xfc\
+\xc6\xa2_\x9fd>\x06Z\xb4s\xd5<c\xca\x91c\
+\x1c\x9e\xd8\x09ik}f\xca\xc1c\xd8\xf6\xd1~W\
+T\x01Z\xbc\xf3\xbf\x1e\xcbL\xf9\xe6s\xb5\xa2Y\xc1\
+\xbb5\xb6\x82m\x8em\x8f}\xc0$@\x8bw\xbe\xfc\
+KR\x81\x8dJ\xb9\x95\xcf\xef\xc4I\xb0\xed\xb1\x0f\xb0\
+/\x98\x02h\xc6\xe6\x0b\x93\x04\xafv\xadBn\xe7\xf0\
+\xc4Y\xb0\x0f\xb2\x0c\x07\xd6E\x0b\x22y\x14\xe0\x19C\
+\x1e\xdb\xcb\x1a\x1ePV4\xcd\xe4=H\x81\xe0n\x86\
+\xcc\x94CoD\xc3\x9dEt\x14F\xe0\x19J\x1e_\
+\x91rk\xa7`\xef\xf4\xad\x90\x9b\xf6%\xefE\x0a\xc4\
+\xed\xd1{k\xcc\xe3\x17l\x98S\xbc\x8d\xbaa<\x81\
+\x07\xe7w\x8e\x90\xd2\xab\xfd\xe78D\xf4\x88SH\x15\
+j\xcc\xe3FGb\x88\x1f\xb1a<\x81'\x89<v\
+\x89\xc1\x83r\xada>pwF\xcf\xe8\xac{\xd2\x89\
+o\x9e\xd8\xb1\xc6HE\x0cD\xe0A\xf3\xf51)#\
+\x03}\x1d\x87\x88\x1e\xc1y\x22\x83\xf6\xd2\xf7\xb8\xb4D\
+C\x10]L\xca\x03\xc1|!\x87\x88\x1e\xc1%%\x5c\
+\x97\x8c+@\xc4\xfaL%\x8f\x97C\xf9\x1e\x0e\x11=\
+\x82\x8b\xda\x9d]\xc5\x0f;\x88&\xf0\xe0\xee\xc1S\xa4\
+\x84\xe5Ky`M\x87\xb8\x84dO\xb5i\xd2\xf0p\
+\xf6\x13u6\x88\xde\x14.<\xdc\x12\xd1#*E\x8b\
+<Uw\xee\xef\xb8;4f.\x8cX\x9f\xc5\xe41\
+\xbb\xb3\xcas\x88\xe8\x10\xdc\x94V\xdb2\xe6\xa3\x98\xb8\
+0\x02O\x1ey\x9c!%)R\x15\xe0\xee,\xfe\x82\
+{\xac\xabL\x93\xc6\x85\xb2Q?\x5c\x17\xf6V$\xe1\
+\xe1\x96\x88\x0e\xc1C\x0dx2&\xaa.\x8cX\x9f\x05\
+\xe4Q\x14\x8d\x0ap\x88\xe8pex6/*.\x8c\
+\xc0\x83\x1b\xc2\xce\x83\xc4l3wg\x89!xx\xb1\
+\xda<!+\x98\x13\xb0\xa1\xba\xb0\xff\x886<\xdc\x12\
+\xc5_\xf0T0\x1e-\x8f\xa8\x05\xf2\xcf\xf9\x5cD+\
+\x17\xab\x8apK\x14?\xc1\xb3\xf8U\xa6\xfbFl\x98\
+\xf3\xc2\xa9HY\xa0\xe2X\xc2\xc3-Q|\x05\x93Z\
+hU\xb5[\x22b\x81\xfc\xdb4\xae\x90\xa2\x8dGe\
+\xb8%\x8a\x9b\x15\x82j\xf3\xf8\xbb\x03\xe5'Bv\x82\
+\xc9\x82\xf1\x5c\xbc\xe0i\xb5D(\xd1\x84H&\x93C\
+\x86\xae\x17\xa4\xebzB\xba6\x07\x0c\x1a#\xa8\xe5Z\
+P)nV\xdb%\xd8\xc1\xe9\xb1\x83\xc9Q\x0b\x0d\xf6\
+Jh\xb0]\x87:[9i@O\x22[!\xc0\xc4\
+^\xe4\xc3\x11a[ b}pg\xe1\xf5x\x02\x14\
+-K\xd4\x9d\xc02\xb0\xfb\x04\xe8\x9bZ\x00\xbd\x0cC\
+@\xad\xd0\x876Z\x11\xacPn\xfa\x01\xae6\x9f\x84\
+s\xf5\x07\xa0\xdev=!\xadPe\xf3\xd4\x01R\xe9\
+\xf6\x82\xb1@\x8bh\x80'R\x96\x08!\x19\x95U\x08\
+\xc33\xa7A\x8f\xe4\xfc\xce\x8dV\xc8\xcf\xea\xd7\xedn\
+_\x99\xd6\xe7I\xb8\xd1r\x01Jk\xf6\xc2\xf1\xea\xdd\
+>\xb8\x12\xc6\x0a\xa9j\xd7\x93\x0f\xa7\x87l\x81\x88\xf5\
+A\xb8~\xc4>\xa3\xa9R\xe1X\xa2$U\x1a\x8c\xcd\
+\xfd)\xdc\x9d=\x174\xca\xa4\xa8\xea\xe7p[\xe0h\
+\xd5v8\x5c\xf1W\xb0\xb8\x9a\x98\x87\x08\x13\x7f6\xda\
+\x86\x1a\xc4\xb2\xc7vd\x81\xe6\xd1\x06O\xa8\x96H.\
+S\xc0\x98\x9c\xf90)o!hBtQ\xe1\x0a\x02\
+:\xa1\xe7\xbf\xc2\xdd=\x1e\x80\xaf\xcb6\xc3\x91\xca\xad\
+\xc4\x15\x08\xcc\x02\x84\x19q1\xad2\xf9p\x99h\x1b\
+\x07\xf8\xde\xc7h\xadT0C\xfc\xdc\x94\x81\xf0\xf4\xc8\
+\x8d0\xa3\xef\xd31\x83\xe7\x1f@\x22\xef\xc4w\xa3\x0e\
+\xa8\x0b\xcb\x829\xb9Cra\xc4}\xa1\xe5)\x03\x00\
+\xaa\xf31\x8b\xbb3\x99\xcf\x02L\xe9\xfd\xa8ot\x15\
+\x8a\xd8\xdc\x1e\xa80\xb9\xa0\x92\x14\xb3S\x00\xbb\xfb\xe6\
+(K\xab\x94C\x8aZ\x019\x06\x15\xe4\x92\xa2S\x86\
+\xf6sq\xb4Vr\xed}8p\xfd\xbf\x81\xcd\xb46\
+2\xa86M\x18\xfb\xf6\xec\xdf\x1d\x09\xd6\x85=L;\
+<b\xeeL%\xd7\xc0\xfc\x01\xc50\xa8\xfb\xc4\xa0\xbe\
+_ \x0dp\xbc\xd2\x0a\xdfVX\xa1\xb4\xca\x0a\xd7\x9a\
+\x9c\x1dv/\xfe\xaa\xf5NS\xc3\xf0l=\xdc\x93K\
+\x82\xf2\x1c=(:\x98\xc9G\x90\xa7\xf6y\x1crR\
+\xf2a\xeb\xf9U\xe0\xf28\x18\x03\xc8\x8b\xae\xec%\xf2\
+\xc1mf_\x0a\xa0\x07Y\xa9Z+Dwd\x1c\x80\
+\x9f\x0fy\x95\xb8\x8bA\x1d~O\x9d\xd5\x0d\x9f\x9fi\
+\x82\xbf_1A\x93]\x08\xb1)\xc9\xc8\x82\x80\x86e\
+\xfb\xb9&H\xd3*`\xea\x1d\x06X08\x0d2\xf4\
+\x81\x07\xb5\x08\xb6aX\x06|\xf8\xc3s\xbe`\x9b-\
+7V>1(\x17\xe6\xdf0v\x8d\xad\xca\xc9\xe1\xcd\
+\xd9\xa9\x04\x9e\x8c\x80_gr\x08\xb0\xe5d=\xec\xbe\
+h\x02\xb7'\xb2\xaeD)\x97Aa\x7f\x03<R\xd0\
+\x1d\x0c\x9a\xc0\xc9Hj\xacW\xe1\xfd\xd2\xa5\xc4E\xb6\
+0\x05Q\x8dy\xdc\x94\xb6\xb7\x0cI\xad\x85\xfd\x84\xa5\
+Ji\x942\xf8\xfd\xb4\xdc\x0e\xe1\xd9}\xb1\x19\x1e\xdb\
+v\x0dv\x9co\x8e8<\xbe\xe1.\xf9\x99\xf8\xb3\xf1\
+\x1d\xf8\xae@\x92\xa9\xef\x0b\x0f\x0fY\xeds\xb9,\x09\
+^\xa7\x15\xcc(l\x16;\xa1\x1d\xc0s\x13\xb2a`\
+\x86\xf4\x5cg\x8b\xd3\x03\xaf|}\x03\xde8T\x03f\
+G\xf4\x87\xd3\xf8\x0e|\x17\xbe\x13\xdf-=J\x1c\x04\
+\xf3\xf3\x9f\xf7\xd7\x82\x0d\xc1\xbb\xd8\x02\x02D\xdc\x17\xf6\
+\xc4DV*\xf4/\xc3\xd2a|^\xb2\xb4\xc9\xb5\xb8\
+a\xc9\x17\xe5\xb0\xffZ\xec]\x05\xbe\x13\xdf\x8d:H\
+\xc6D\x19\xf7\xc1\xd8\x9c\x05\xcc\x00\x84\x17\xf9\xf9n\x83\
+\x0c`\x81\x10\x1e&\xec\xea\x00bu\x16\x16H_\x17\
+\x86A\xee\xd2\xdd\xe5p\xdd\xe4\x8c\x9b\x8e\xf8n\xd4\x01\
+u\x91\x92\xe9}\x17AN\xf2\x006,>\xde\x02\xe9\
+\x95=\x1d\x08\xa0\xc9L\xf8b\x12\xf4\xffj|\x16\xc8\
+e\xd2\x96\xe7\x85\xbd\x15Pou\xc7]W\xd4\x01u\
+\x91\xb2Dr\x99\x12\xe6\xe5/\xf7\xcd\x9a3\xd1\xf6\x0a\
+ka \x80\xc6\xb0P\x89y\x83\xd2 /U-\xfa\
+9\x0b\x89;\x8a\xff\xb7\xc27T\xa7EP\x17\xd4\xc9\
+\x22\x11\x13\x19\xf5}\x88+cc\xec\x82\x97\x18\x8b\x02\
+D\xe2\x1f\xfc\x15\xb8\x87\xf6\x0a\xe0\xbc\xcb\xc3#\xd2%\
+?\xff\xfa\xa1\xea\xb8\xba\xad@\xee\x0cu\x93\x92Iy\
+\x8f\x80^\x95J=@x\x036^\xa3.f\x81\x90\
+\xacd\xda+0\x9fX\x1f\xbdJ|\x92\xfc\xcbK\xa6\
+\xb8\x04\xcc\xa1\x04\xd6\xa8\xa3\x98\xa8\x15:\xb87\xf7\x9f\
+\x19\x88\x83\x04<\xb91[\x0c\xa0\xa1\xf4\xd3/\x87\xa2\
+\x01i\x92\xc3\xe7\xf7\xbe\xab\xa3\xbe\x03PG\xa9\xe9\x04\
+\xdcn\xa2UR\xff;\x0cr\xb9s\x8a\x18@\x83i\
+W\xfc\xfe;\x0d\xbeYg1\xd9r\xb2!&\xf3<\
+\x9d\x15\xb3o6\x5c\xfc\xc8\x15n\x05\x19n\x9cF}\
+\x1d\x94r\xcbH1\x80\x86\xd1\xae8\xae9\x89I\xa3\
+M\x80/:\x98\xfd\xa5IPW\xd4YL\x0a\xb2\xe8\
+?\x89\xa2R\xb4\xf4\x11\x03\xa8\x1f\xcdJ\xf74\xa8\xa1\
+\x7fw\xf1)\xaa\xadg\x1b\xa3\xb2<\x11-A]Q\
+g1\xc1\xad\xb6\x19\xfa<\xca\x012\xa5\x89\x01D\xb5\
+\xd6\xe3\xf2\xc4\xb7\xa2z\x097{.\x9b\x805A\x9d\
+\xbd\x12\xcc\xf7\xefF\xf7`X\xad0iZ\xf3\x09\xc9\
+\xfdCx\x1c?R=\x86\x1c\x91-\xbe\xab\xf0d\x95\
+U\xd2\x1d\xd0,\xa83\xea.&\xfd\xd2FS>\x12\
+sCm\xcb\x98\xfc\xb6\x16(\x97f\x85q\xc6y\xb0\
+Q|\xc1\xf4\xf0u\x0b\xb0*R\xba\xe7\xa5\x0e\x07\x19\
+\xe5\x8b\xac\x82G3\xb4-@\xe9\xb4\xc7?Rs?\
+\xa7\xaam\xcc\x02$\xa5;n\xf3\xe8\xa6\xcd\xa1\xdc\x0a\
+\x09y\xcc\x00\x84\xfb\x90\xc5\xc4\xe1\xf6\xc2\x95F\x07\xb3\
+\x00\xa1\xeeX\x071\xc9L\xeaK\xb5\xeer\x10r\x98\
+\x01('E|\xdd\xab\xd2\xec\x94\x0cDY\x10\xd4\x1d\
+\xeb &\xe9\xd4[ wv[\x80\xb44+kL\
+RJ\x00\xe4\x02\xd6E\xaa\x0e\x0c\xac\x8bi\x98\x01H\
+\xab\x14\x0f(\xcdN\xf6\x93\x1bH\xd5\x815\x80\x80n\
+\x80\xc4\xd5\xb4\xb9\xd8\x07H\xaa\x0e\xb4\xef\x0f\x92\xcb\x9d\
+\xec\x00\xc4\x85\xe6`\x9a\x01i=!\xda^t*\xf6\
+\xf9\x97\xaa\x03\xed\xe7\xe9=\x1e\xb5\xa3-@v\xba\x01\
+\x12\x1fj\xa5\xa8\xd9\x07(Y\x02 \xab\x8b\xfa\xc5a\
+v\x00\xaa\x95\xd8O\x9c\x93\xa2b\x1e \xa99.\xb3\
+\xb3\x9e)\x80\x1ah\xd6Tj\xae\x04\xe7\x87d\x0c\xdf\
+\x0b\x8d\xbaK\xcdq5;j\xa8\xd6\xdd\xebUV1\
+\x03\x10f\xcc\x10\x1dG\x92\xe1\xfd\x1d\xdd4\xcc\x02\x84\
+\xbak$\xa6(\xaa-\x97\xe9\x8e\x81@Q\xc9\x0c@\
+\xb8!\xdd*1\xdc\x1d\x96\xa5c\x16 )\xdd\x1d\x82\
+\x15\x1a\xed7(\xb7@\x8a\xb2\xb6\x00UPM;\x89\
+\xa1\xcf\xd4\x8a\x87ic{&1\x0b\x90\x94\xee\xe5\xa6\
+\xd3\xd4\xeb\xae\x90;N\xdf\x02h\xcf\xc2|\x0c\xf9\xa9\
+\x0e\xfb\xbf\x97\xd8;S\x90\xad\x87n:\x05s\xf0\xa0\
+\xce\x05\x12{\x9c.5\x1e\xa5=\xfe\x01c\xf2\x91\x0b\
+m-\x10J\x19\xcdJ\x1f,\xb3H\x06\xa23\xfa\x19\
+\x98\x03\x08u\x96\x1a\x00\x5ch8L\xb5\xeeN\xc1\xe0\
+X9\xb1\xc4\xdb\x1e\xa0K\xb4\xc7A\x17\xeb\xc5\xb7n\
+\xcc\x1f\xd4\xcd\x97\x9f\x87\x15A]Qg\xd1\x01\x83\xf9\
+\x1c\x89\x7f*\xa9\xd6\xdf%\x18n\xa5\x9fm\x0b\x10\xf5\
+\x8e\x173\x8aI\xb9\x83Y\xfdS\x99\x01\x08u\x95r\
+\xbb\xc7\xabwQ\xaf\xbfKH\xfeQ\x0c\xa03\xb4+\
+\xfe?\x97L\x92\xe7\xcb\x1f)H\x87\x14\x0d\xfd\xb1\x10\
+\xea\x88\xba\x8a\x8e\xbe\xdc\x168]\xfb\x15\xf5up{\
+\x92N\x88\x01t\x8av\xc5q\xe5z\xc7\xf9&\xc9\x8e\
+y\xf2\xae\x0c\xea\x1b\x1fu\x94\x02\xfdp\xe5gLd\
+\xb9\x17<\x9a}b\x00\x9d#\x85\xfa\xa4}[\xcf6\
+I\xce\x09\xcd\xbc\xd3\x00\x13{\xd3{4\x18uC\x1d\
+\xa5\xac\x0f\x02D\xbbx\xbd\x0a\xd0(\x1bw\xde\x06\x10\
+\x19\xca\xe3\xf2\xef\xb7\xb4W\x00\xb3\xaa~\xf4\xbd\xf4\xbc\
+\xe7\xd2{\xb3|\x9b\xf0i\x13\xd4\x09u\x93\x92}e\
+\x1f0\x91t\xd3\xe6\xca2\xad\x9b\xb9\xce*f\x81P\
+\x8e\xb0\x10\x84n#V\xa8\xacY|},Y-\x87\
+U\xd3s\xa1\xbb^I\x8d\xbe\xa8\x0b\xea\x94,\xb1{\
+\xa0\xb2\xe5<|{c\x1b\x0bMO \xcf8\xdb\xf6\
+\xdf\xedk\xb4\x8f\x85J`\x82\xf0\xd7\xbe\xa9\x06\xa9\xd3\
+\xcc\x99IJ_\xe6V\x1a B\x1dP\x97L\x89}\
+\xdd\x82\xd7\x0d;.\xfe\x91\x99\xbb\xc7\x04A\xbf;\x10\
+@\xfb\xc1\xbfLO\xbb\x9c\xaf\xb3\xc3\xe6\x93\xd2[\x1e\
+\xfa\xa4\xa9\xe1\xf5\xfb{\xc6\xd5\x9d\xe1\xbbQ\x07\xd4E\
+rdy\xf9-\xa8\xb2\x5cb\x02\x1e\xafW\x8e\x99\xc5\
+\xdf\x96\x04\x88\xc4Av?DL\xc8\xa7\xa7\x1a\xe0\x9b\
+2\xe9\xb8!+Y\x05\xebf\xf5\x8aK`\x8d\xef\xc4\
+w\xa3\x0eR\x82\x17\xd5\x1d\xab\xda\xc1Js\x83\xd5\x99\
+\xdb\xb8\xbepUM \x0b\x84\xf2\x05+\x15B\x0f\xf6\
+\xea\x81*8W'\xbd\x1f\x0e\xe3\x8e\xdfL\xeaA\x02\
+\xd8\xcc\x98\xcc\x13\xe1;\xf0]\xf8\xce\xe4\x0evL\xf6\
+M\x1b\x15\xd4\xd5\x0c\xf4\xc4?\xc6\x83\xed\xffO\xac\x86\
+\x9f\x01C\x82';\xffsoE\xc0T\xba(\x85\xfd\
+S\xe1\xcf\xf3zC\xd1\x80\xd4\xa8,{\xe0\xcf\xc4\x9f\
+\x8d\xef(\x0crV\x1c\xaf\x84\xfa\xf9\xd05\xcc@$\
+x\xb4k\xdb\xff\x9f\xd4uO\xdf\x91\xc7(\x96@\xc2\
+\xcce\x18\xac\x06\xcaZ\xdf*\x985\x15\xe7\x93\xf6^\
+\x0e\xfd\xb2\x95\xf6\x82I?\xa7\xf53\xf8r7f\x84\
+\x19\xb4\xe3\xfe\x9f\x0fO/\x87\x0a\xf3Yz\xad\x8f\xcb\
+h[=\xe5S\xfd?\xc6D^I\x80\xf0\xa6\xe6\xd5\
+\xc0\x98\xe0\xee>\xbc\xfa P\xf6\xfa\xf6\xa3\xb9X\x5c\
+\xf7\x94\x08\x10\xd5[F\xedy\xf3\xfe\xd7f\x06\x0b\x10\
+\x1e\xcc.\x07\x06\xcf\x8daM\xf0\x0a\x04\xccb\x1f\xaa\
+\xa7j{\xe1\x5c\x8bS\xf0\xfd\x1b\x05/\x98K\xee\xc4\
+\x85s.\xc1\x0b\x9b\x8e\xd7\xf9\xb2\xebO\xee\x9b\xc2$\
+DU\xa6\x7f\x1a\xf7\xce\xec\x15\x87\x82\x02\xc8\x0f\x11\x06\
+\xd3\x85\xc0\xa8`ga6{\xa9\x84\xe4\xb1\x92\xab\x8d\
+\x0eXs\xa0\xda\x97\x89\x03\x81^N,$k\x10\xb5\
+8\xf2\xea_\x9b\xf6A\xc6\xed\xc3zo@\x0b\xf3g\
+`Xp\x9e\xe8\xa9\xede\xb0\xe1X\x9d\xe4\xdaYT\
+\x87\xbc\xe4\x9do\x1f\xad\x85gv\x96\xdfJA\x83\x13\
+\x9fk\xc8\xa8q\xdfU3S\x81\xb5\xc5\x99\xb7E\xea\
+s\x81\x00\xc2\xb9\xf5\x0a\x96!\xc2\x18\xe7\xb33\x8d\xf0\
+\xe8\xd6\x1f\xe1\xd3\xd3\x8d\x92[A\x22\xdb\xd8\x1e\xf8\xb8\
+\xb4\x01\xfe\xed\xf3\x1f}K.B\xbb\xfc3\xacA\x84\
+\xd7~\xcbe\xae\x17%C\x06)\x17\xe6wc\xc5\xe4\
+\xf1{H\x10\xc1c\xc4\x98k\x1a\xd3\x05Ke|\x0d\
+W.58|\x17\xcd\x95\x5c1\x07e\xf1Xqg\
+\xf5\x96\xbb\xf6\xbey\xff\x1f\xa6\x8b}.`\x0c\xe4\x07\
+\x08\xefS\xba\x0e\x94\xa7\x7f\x09Gp\x99\x013\xbfb\
+\xf2N\xcc\xbf\xa8\x0f\xf1\x9c=\xeeM\xc2\x09\xcc\xa3d\
+\x04w\xe4\xba%\xac\xfb9h\x87\xc8\xe3UAe\xf3\
+\xd4\x01\x1b\x8b\x96_\x08\x0b ?Do\x90\xc7\x12H\
+`\xc1\x8eD\xa0p\x84\x85'E1\xa1\x15\xe6$j\
+M+\x83\xa3(\xbc\x06\xbc\xde*@U\x8b\x0b\xca\x9b\
+\x9dPn\x8aLv4\x9a!j\xb2\x0e)}c\xe6\
+\x9b#\xa4>\x1f\xe8\xda\xef\xb6\xf2*)\x8b\x13\xd1\x0a\
+\xb5\x8dKp{\xc8\xcd-\x22\x96\x98\xbf\x1bc\x22\x14\
+)\x88Zc\xa2XB\x84\xd6\xc7\xe66>\xde\xe1/\
+@G_\xb0ga>\x1e\x91|\x17\xb8D\x1d\x22\x9a\
+\x02\xeb&\xdb\xa0\x93\xef\xce^q\xac\xd3\x00\xf9\x05\x03\
+i3\xef\xea\xae\x01\x11\xb1>^\xbb+kaP.\
+8\x98/\x22V\x08\x97\xf0W\xf3n\xee\x1a\x105Z\
+\x87\x95l\x98S\x5c\x1a1\x80\xfc\xf2:\xdc\x5c\xde\xe0\
+\x92\xc0\x109\xddin\xab3\xe7\xc1\xa0\x07\x01\xc1~\
+!\xb1B\x98V})\xef\xe2\xc4\x86\xa8\xc96x\xdd\
+\xa6\xb9\xcb\x82\xce\xd6\xd2\xe10^dX\xbf\x9d<\x8a\
+x7\xc7fz!\x96C|\xb3\xbd_\xf5\xda\xe9\xef\
+e\x07\xfb\xf5\x1d\xad\x85I\xc9/b>\xd6\xe5\x96(\
+\xea\x96H\xf0h\xbc&{\xbfy!C\x1e\xea7\x10\
+W\x86Y<\x96\xf1\xeeM,\x88\x1a,#?y\xaf\
+\xe8\xf9\xc3Q\x07\xc8\x0f\xd1\x06\xf2\xd8\xc9\xbb71 \
+\x22\xae\xab\xc6\x98r\xf8ga\xb9\xd9N\xd4\xeb\x09R\
+jy\xf7\xb2\x0d\x91KH\xf64\xdb\x06Ni\xcd\xf7\
+\x133\x80\x88\x15\xaa&\x8f\x87\xb0n\xbc{Y\x85H\
+\x86\xab\xed\xc5\x1b\xe7>\xfbC\xd8\x81~g*D \
+*!\x8f\x17y\xd7\xb2\x09Q]\xcb]{\xde\x99\xfd\
+\xdb5\x9d\x1a)F\xa0N\xab\xe0\xe6\xe63.\x0cA\
+d\xb2\xdfy\xc3\xe6\xca\xee\xf4\x96\xe5N\x03D\xac\x10\
+\xfaN\x0c\xc0\x8e\xf3\xaee\x03\x22\x02\x8e\x8d\xc4=\x05\
+\x9b\xe6.\xf3\xc4\x1d ?D\x98\xeec\x0e\xf0\xa5\x0e\
+\xea!r\x0a\xa9B\x83e\xf88\x02ODR\xe1\x87\
+<\x13\x1dHfl\xbe\x807\xf9~EJw\xde\xbd\
+\xb1\x93`g\xac?(]\xe1=^\x99\xb1`\xc3\x9c\
+\xe2\x88\x84\x1cA\xedH\x0c\x03\xa21h\x94H1\xf0\
+\xae\xa5\x07\x22/\xf9\xf3\xf5\xb5\xe3\xefN\xee3\xfa\xa9\
+H\xbd3\xdc\xa5\x8c\x8e\xdc\x19&\xa9\xc2\xb52\x1b\xef\
+Vz\xdcY\xc9\xd5\xa3\x1fF\x12\x9e\xa8\xb8\xb0v\x96\
+\xe8>\xf2\xd8\xc1-Q|-\x11\xee\xdb>~\xc3\xfa\
+\x87\xd1\xb9I\xcb#\xfd\xae\xa8\xb80\x11w\xb6\x8b\xc7\
+D\xf1\x81\x08s\x14\x9d\xb8a\xfd\xed==\x93_\x8a\
+\xc6{\xa2\x0eP\x9b\xc0\x1a\x8fI\xf7\xe2]\x1bS\x88\
+\xca\x7f96s\xf9\xac\xfc\xb4O\xa2\xf5\x8e\xa8\xc4@\
+\x221\x11f\xc0GK\xc4\xe7\x89b'\xc7IL4\
+&\x9a\xf0\xdc\x025\x16\xb5\xf1\x9f\xec\x98H\xca\xdfx\
+\xdfF]\xb0\x8d'\xfa\xdb\x1c\x12\x02 ?D8\xd9\
+8\x9f\x94\x17\x80/\xc0Fe \xe6o\xdb\xf9\xfe\xb6\
+\x8e\x89D=\x06\x92\x88\x8b\xa6\x90\x07\x9aW#\xef\xf7\
+\x88\x08n\xaby\xc8\xbf\xb8\x1d3\x89I\x0c$a\x8d\
+\xb0\xa2\xc3\x80oJ\x8b\x84`\x1b\x0e\x8b5<q\xb5\
+@\xed\xac\x11\x1e\x9b\xc6\xe4\x8dI\x9c\x85\x90\x04\xf7\xa5\
+?K\xc0\x89\xdb\xa9\xe1\x98\x0c\xe3\x83\x84(\x8f<\xde\
+\x02~\xda#X\xc1\x09\xda_\xf8\xf7\xa7C\x97\x07\xa8\
+\x0dH\x0b\xc8\x03\xb3\x81\xf09#q\xc1\xdd\x0eK\x09\
+8\x9f\xd3\xa0L\xdcb\xa0\x00\xb1\x116\xcc\x00\xffh\
+\x82\x9f\xc5\xff\x7f1\xfb\xdbd\x00-\xf0P\x13\x03\x05\
+\xb0Fx7\x12fHK\xe8\xd42\x1d\x08\xa6\xe0\xc7\
+\x18g\x95\x7f\x0f:UB\x9d\x0b\x93\x00\xa9\x07y<\
+\xd7\xc5@j\x05\xe7\xd5XM\x08&,@m@\xc2\
+\x05\xd9E\xa4<CJn\x82\x82\x83IM\xd7\x93\xb2\
+\x81\x80SO\xbb\xb2L\x01\xd4\x06$\xcc\xaa\x863\xda\
+\x8f\x92\x82\x99\xd3\xe5\x8cC\x833\xc8_\x92\xf2>)\
+[\x098nV\x14g\x12\xa0v0\xa1%z\x98\x14\
+LG2\x8a1\xf5qq\xf9/\xa4|D\xa0a2\
+\x9d2\xf3\x00\xb5\x83\x09\xe7\x92~\x0a7\xb3\xeb\xe3\xc2\
+\xad\x862\x151\xdb8\xde\xc5\x867\xfe\xfd5\xdes\
+8\x1c\xa0\xc00i\xfd\x10M\x86\x9b[I\xee!%\
+\xd6\xb7\xce\xe1Mxx\x891n\xf1\xc5\xabD\xf7\xfb\
+/\xf4K\x18IX\x80D\x80\xc2\x9b\xe6\xf0\x5c\x0bn\
+n\x1b\x027\xd7\xe1\xee \x05\xadVj'\x7f|3\
+)hM\xae\x90r\x8a\x14<&\x8c{\xa0\xce\xfao\
+\xc2NX\xe92\x00u\x00W\xaa\x7fT\x97\xde\xa6\xe8\
+D\x5c \xba <(\xd0\xd0\xa6T\x10H\x9a\xbbj\
+\xdb\xdd\x02\x88\x0b\x97p\xe5\xff\x04\x18\x00\xc3:\x8dd\
+\xf2\x87\x09m\x00\x00\x00\x00IEND\xaeB`\x82\
+\
+\x00\x00\x06\x87\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x19tEXtSoftware\
+\x00Adobe ImageRead\
+yq\xc9e<\x00\x00\x03#iTXtXML\
+:com.adobe.xmp\x00\x00\
+\x00\x00\x00<?xpacket beg\
+in=\x22\xef\xbb\xbf\x22 id=\x22W5M\
+0MpCehiHzreSzNTc\
+zkc9d\x22?> <x:xmpm\
+eta xmlns:x=\x22ado\
+be:ns:meta/\x22 x:x\
+mptk=\x22Adobe XMP \
+Core 5.6-c140 79\
+.160451, 2017/05\
+/06-01:08:21 \
+ \x22> <rdf:RDF \
+xmlns:rdf=\x22http:\
+//www.w3.org/199\
+9/02/22-rdf-synt\
+ax-ns#\x22> <rdf:De\
+scription rdf:ab\
+out=\x22\x22 xmlns:xmp\
+=\x22http://ns.adob\
+e.com/xap/1.0/\x22 \
+xmlns:xmpMM=\x22htt\
+p://ns.adobe.com\
+/xap/1.0/mm/\x22 xm\
+lns:stRef=\x22http:\
+//ns.adobe.com/x\
+ap/1.0/sType/Res\
+ourceRef#\x22 xmp:C\
+reatorTool=\x22Adob\
+e Photoshop CC (\
+Macintosh)\x22 xmpM\
+M:InstanceID=\x22xm\
+p.iid:7F517578FC\
+EF11E793C7AB30FF\
+B47C13\x22 xmpMM:Do\
+cumentID=\x22xmp.di\
+d:7F517579FCEF11\
+E793C7AB30FFB47C\
+13\x22> <xmpMM:Deri\
+vedFrom stRef:in\
+stanceID=\x22xmp.ii\
+d:7F517576FCEF11\
+E793C7AB30FFB47C\
+13\x22 stRef:docume\
+ntID=\x22xmp.did:7F\
+517577FCEF11E793\
+C7AB30FFB47C13\x22/\
+> </rdf:Descript\
+ion> </rdf:RDF> \
+</x:xmpmeta> <?x\
+packet end=\x22r\x22?>\
+@\xb2\x97\xa1\x00\x00\x02\xfaIDATx\xda\xacW\
+Ak\x1aA\x14~\xabV*z\x08\xd4@\x0d\x05i\
+\xa99\x05J\x14$ \xb94\xc5KI\xbd\x19\x08H\
+\xb1`1\xc6\x1e\xfa\x07z\xb0\x90\xdc,R\x1b\xa8=\
+Ti+\x18\xf1RA/\xa5\xa5\x16\xa1\x98DC\x0e\
+\x0d\x89\x05/\xb6\x87b\xa5\xa0\xa2P\xa2}\xb3]\xb7\
+\x094\xee\xcc\xee>\xf8|\xeb\xec{\xf3}\xfbfv\
+f\x96\x03F\x0b\x87\xc3W\xd0=D\xdcA\x5c\x17\x9a\
+\xbf\x22\xde\x22\x9e\xc4\xe3\xf1&K\x7f\x1c#\xb9\x1f\xdd\
+3\x84\xe1\x9c\x90>b\x1dE\xbc\xa4\xedS\xcb@\xbe\
+\x8e\xee9\xe2\xc2\x840r\xcf\xe3t:[\x95Je\
+G\xb5\x0a \xb9\x1d\xddg\x09\xf2\xd3\xf6\x1b\xb1\x80\x95\
+\xa8J\x05j(;\x8c1\x90\x8f+\x11S\xa5\x02\xf8\
+\xf4\x0et\xbb \xcf\x9cX\x85\x1d\xa5\x15X\x06\xf9v\
+[\x8d!XP @2WG\xd1\xc9e\xf2c\xb7\
+\xdb\xc1\xeb\xf5\x82\xd1h\x9c\x18\xdc\xeb\xf5`{{\x1b\
+\xaa\xd5\xaa\x98\xab\xb4\x02\x179\x8e\x83\x95\x95\x15Ir\
+b$\x86\x08\x15\xcc\xa0\x86\x80\x1f\xe3\x8b\xd1h\x04\x1b\
+\x1b\x1b\xb0\xb9\xb9y&\xe0\xbc\xf6\xd3\xb9J\x04\x1c\x12\
+\x02R\xd6\x93\x93\x13\xc9`\x12\x93\xcdf\xc7\x7f\xbf\xa8\
+1\x07>!\xee\xef\xed\xedA\xa3\xd1\x00\x97\xcb\x05\xb3\
+\xb3\xb3\xd0\xedv\xc5!!\xe3\xbe\xba\xba\x0a\xc7\xc7\xc7\
+\x10\x89D\xa0\xddn\x9f\xceU,\xe0\xfd\xf8\x82t\x9c\
+\xcf\xe7Y\xde\x82\x0f\x8a\x87\x00\x17\x92\xef2\x17\xa2]\
+\xcc\xfd\xa6\xd6R\xfcZ\x86\x00\xaa\x1cZ\x01o\x10\x03\
+\x06\xf2\x81\x90\xa3\x8e\x00,e\x0b\xdd\x16\x83\x80-!\
+G\xb5\x0a\x10{L&<E\x5cO\x88\x05U\x05\xe0\
+\x13\xfdBw@\x11z \xc4\xaa+\x00\xb7e\x0b:\
+\x1bE\xa8\xadX,\xde\xc0\xc5\x8b\xea\xb0\xc3M \xbc\
+\x8a\xee\x16\xe2&\xd9\xd7\x11\xd7h:\x5c\x5c\x5c\xe4\xf7\
+\x82~\xbf?\xdc\xdf\xdf?L\xa7\xd3\xe4`\xf2\x0e\xab\
+\xd2\x90\x14\x80\xa4&t\x01\x84\x0f1\xcf\xfa\xde\xb9\xdd\
+nX^\xfe{|h\xb5Z\x10\x8dF\xa1\xd3\xe9\x8c\
+o\xd7\x10\xaf\x10/PL\xf7\x8c\x00$&\xfe\x01\xe2\
+\x11\xe2\x12+1\xd9-=\x1e\x0f,--\xf1\xff\x09\
+)!'\x22\xfec?\x11\x11\xc4S\x142\xe2\x04\xf2\
+\x94\xf0\xd4\xcc\xa6\xd1h\xf8\x92\x93=\x82_\x00\x06\x03\
+\x88\xc5b\xd0lJ~\x1e\x90j\xdc%{\xc1=\xb9\
+\xe4Z\xad\x16|>\x1f8\x1c\x0eq'L$\x124\
+\xe4 p~$\x02\x82r\xc8\xf5z=\xf8\xfd~\x98\
+\x9b\x9b\x13\xcf\x04\xc9d\x12\xea\xf5:K7A\x9d\x9c\
+\xc9f0\x18 \x10\x08\x80\xcd\xf6\xef\xad\xccd2\x80\
+\xb3\x9e\xb5\xaby\x1d\xcb\xd7\x111\x93\xc9\x04\xc1`\x10\
+\xacV\xab\xd8V(\x14\xa0\x5c.\xcb\x1aE\x1dK\xf4\
+\xd4\xd4\x14\x84B!\xb0X,b[\xa9T\x02\x5cx\
+d\x1f\x9b\xa9\x05LOO\xf3\xe4f\xb3Yl\xab\xd5\
+j\x90\xcb\xe5@\x89Q\x09\x98\x99\x99\x81\xb5\xb55\xbe\
+\x02c;::\x82T*\x05\xc3\xe1P\x91\x80?\x02\
+\x0c\x00\xae\x14\xfd~;\x03\x1c1\x00\x00\x00\x00IE\
+ND\xaeB`\x82\
+\x00\x00\x06m\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
+\x00\x00\x06$IDATX\x85\xc5\x97]l\x1cW\
+\x15\x80\xbf{g\xf6\xd7\xeb\xdd\xecn\xd7v\x8a\x93\xaa\
+\x09N\xec@\xab\xa4\x15\x85\x22\x81\x08/\xfd\xaf\xa1\xa1\
+\x91\xa8\xaa*o}\x02\xa9\x02\xc1\x03\x0fHH\xbc!\
+\xc4\x8f\xfaP\xde\xc2\x03Q\xd3\xa2\x22\xa52\xa9x\xa8\
+\x10T\x94\x86\xa8\xad\xed\xc4I\x14\x9aD\xae\xecl\xd6\
+k{\xed\x9d\xdd\xd9\x9d\xb9\xf7\xf2\xb03\x9b\xd9];\
+N\x11\x12W:\xba3g\xee\x9e\xf3\x9d\x9f\xb9w\x16\
+\xfe\xcfC\xdc\xed\xc2\xb3Ph\xc3Sq\xdb~\x01)\
+\x0fj\xa5\x8aJ\xeb!KJ\xc7\x92r\xc5h\xbd\xe0\
+)uJ\xc0\xcc\xd3\xb0\xf6?\x038\x03\x13\xb6e\xfd\
+F\xc37J\xa3\xa3z\xf7\x17\x0e\xa53\xbb\xc7H\xe4\
+\x0b\xc4s\xc3\xb4k\x1b\xb8\xab\xab8\xcb7Y\xbep\
+\xb1Q)\x97-a\xcc_<c^\x99\x86\xab\xff5\
+\xc0i\x88g\xa4\xfc5p\xe2\xf3\x0f\x1d\x89\xdf\xff\xf8\
+c\xd2\xf2=XY\x05\xc7\x81V\x1b|\x0f\xec\x18$\
+b04\x04\xc5<\xca\xb6\xf9\xf7\xcc;\xfa\x93\xd9\xb9\
+\x16\xc6\x9c\x5c\xd4\xfa\xfb/\x83\xf7\x99\x00f\xa0\x84\x10\
+\xef\xdc322\xf5\xe0K/&\xe3\xca\x87k7\xa0\
+\xe9\x0e\xac5\xfd\xd7\xe9\x14\xdc7N\xdb\x92|\xfc\xfb\
+?\xb8\xab\x95\x95\x0b\xda\x98\xc7\x9f\x81\x95\xbb\x02x\x1b\
+\xf2B\x88\xb9}SS\xa3\x07\x9f?fs\xf92\xac\
+o\xdc\xd1\xa91f@\xcf\xae\x1c\x1c\xdc\xc7\x957\xff\
+\xe4\xdfX\xb8|\xd31\xe6\x8b\xc7\xa1\xd6\xef\xcf\x8a\xde\
+\x9c\x06+)\xc4\xbb\xfb'''\x0e\x1c\xfb\x96\xcd\xdc\
+<\xd4\x1b\x83Q\x06Nu\xf4>2k\xc0\xb8-L\
+u\x8d\xe2\xd7\xbf\x22\xcdJ5\xe5VW\x8fN\xc3\xc9\
+7\xfa8e\xf4&+\xe5\xcfK\xc5\xc2\x03\x13\xcf\x7f\
+\xdb6s\x17;FB\xa3\xc6\xf4\x88\x898\x0bE\x85\
+\xce\xc3\xf5M\x175{\x89}\xc7\x9e\x8d\xe5\x8b\x85\xc3\
+\x19)\x7f\xd6\x9f\x81n\x09\xfe\x08\xbb3R.\x1c}\
+\xe5{9\xb9\xb8\x04\x1b\x9b\x03\xa9\x8d\xce\xfd\xba0+\
+\x03k\x8c\x81l\x063>\xca?~\xfb\xbb\xf5\x96\xd6\
+\x93\xd3P\x1e\xc8@Z\xca_\x1e84\x95\x91m\x0f\
+]\xdb\xe8F\xa9\xb7\x910J\x13dD\x05%\xd1\x11\
+}X&\xbdQG\xf8>{'\x0fd\xe2R\xfeb\
+\xa0\x04\xa7!\xa7\x8d\x99\xbe\xf7\x99',ucq[\
+\xc7\x03\xa5\x88>\x0bK\x13\xe8U\x1f\x88\xba\xbe\xc4\xbd\
+O|\xd3V\xc6<7\x03\xd9\x1e\x80\x14<U,\xe4\
+\x95P\x0a\xddj\xf7:\x8eD\xa9\xb7\x88\xb2\xab\x17\xa2\
+#\x91\xf4\x87:%%\xaa\xed!\x84$\x97\x1d\xd6\x1e\
+<\xd6\x03\x10\x13\xe2\xbb\xa3\x13\xfb3\xaa\xba\x8e\x89\x18\
+\xd1\xa1\x11)1Rv\x0d\x9a@\xa7-\x0bcY\x18\
+)1\x11\xbd\x0a\xd6w%\xd0\xfbk\x1b\x14\xf7\xed\xcd\
+\xd8B\xbc\x10\x02\xd8A\xc3L\xa6\xf7\xeeA7\x1a]\
+C\x08\x01\xaf\xbf\x1e-\x17\x82\xcfpx\x04\xa3y\xfc\
+x\xa7\x99\x85@7[\xa4>\xb7\x1b\xf1\xd1\x85\xa9\x1e\
+\x00\x0d\xa5\xf8H\x09S\xaev\x88\x83\x0c\xc8\xed\xacF\
+\x861\x06\xadu\xf7Z\x08\x81\x94\x12!:\xa8\xa1=\
+\x00\xed\xf9\xc4\x8a\xbb0P\x0a\x7f/\x01\xb41\x99X\
+!\x8fV\xaa\x93\xda u;\x0d\xad5J\xa9n\xa3\
+\x85\x10J)|\xdfGk\xdd\xe9\x95\xb0\x17\x8c!\x96\
+\xcb\xa0\x8d\x19\xee\x01\x90B\xd4\xdb\x8e\x83\x8e\xc5o\xd7\
+{\x07\x00\xa5T7\xf2;\x016\xce\x9f\xc7\xabV;\
+\xbd\x12\x8b\xe1\x96+\x08!6\xc356\x80\x80[\xcd\
+J%7\x94\x8c\xa3[\xad\x1d\x8d\xee\xe4\xb8\x07\xb4\xd1\
+\xc0\x9f\x9fG\xa4\xd3\xc4\xee\x1f\xc75\x1e\x02*=\x00\
+\xc0\xa5\xfa\xd5k\x13CG\x0ec6\x9dN\x03\x02\xee\
+\x89\x13\x9d\xd4\x1a\x83_.\xe3]\xbf\x8eq\x9c\xee\x0e\
+\x17\xf6J\xcf\xee\x17\xd9={\xf4\x8e\x83\xe5:46\
+\xeah\xb8\xd8\x03\xe0\x1bs\xaa<\x7f\xf1h\xe9KG\
+2=MS\xaf\xa3*\x15\xfc\xa5\xa5\xce\x1b\xb2\xcdV\
+\xdb\xaf\xeb\x07\x0c\x9f\xc9\xb1\x02\xb5\xcb\xd76\xb51\xa7\
+z\x00\x5c\x98\xd9\xd8\xac[\xed\xc5O\xf0\xfe\xb5\x801\
+\xa0[-\x8c\xebn\x19\xe5\xddD\xdf\x03d\x0c2\x9d\
+\xc0\xc4$\x8e\xdb\xb2\x81\xb3!\x80\x048\x0e5!\xc4\
+[\xcb\xff\x9c\xf5\xe5}cx\xb5\x1a\xcau\xbb\xa7[\
+\xb8\xf3\x85\xa2\xb8\xbd\xd5\xf6o\xcd\xa1^\x85\xe7C\xa0\
+\x8f\x1d\xd8C\xf9\x83\x05_\x1a\xde\x98\x86\xcd\x1e\x00\x00\
+W\xeb\x1f./\x95\xeb&\x9bF\xe4\xb3\xb7\x9dE\x0e\
+\x9a\x01\xa7\xdc>\x86U\x1f`\x14\x5c\xe63\x90\x8eS\
+\xbd\xb5Z\xf7\xe1GDF\x17\xe0\x18,#\xc4kW\
+\xdf\xfd\xa0\x91xx\x12\x93\x8c\xef\x18\xbd\xda\x02&\x0a\
+\xae\x8d\xc1\xa4\x12\xa4\x1e\x9a`\xf1o\xb3\x8e\x14\xe2\xd5\
+\xe8Q\xdc\x03\x00P\xd7\xfa'N\xd3\x9d\xfd\xf4\xef\x1f\
+z\xc9G\x1f\xc0\xa4\x12\x03\xa9\xdd*\xfa\x9elE\xa2\
+'\x9d`\xe8\xcb\x07Yzo\xae\xed\xb6\xda\x1f\xd5\xb5\
+\xfe)}c`k\x7f\x0bv\xd90_(\xe5G\xc6\
+\xbfv$\xe6\x9c\xbf\x84\xbfR\xebm\xb8-:<\xaa\
+\x07\xb0K9\x86\x0e\xefg\xf9\xbdyo\xb3Z[n\
+\xc2\x83[}\x13ny\xb6\x9c\x81{\x04\x9cM\xa5\x92\
+\x87\xf6\x1e}8e\x1a.\xcd\x0b\xd7QNs\xdb\xd7\
+0\x14k8Mjr\x1c\x91\x8a\xf3\xe9_?nz\
+n{\xae\x0dO>\x07\xd5\xad|m{\xb8\xbd\x06\xb1\
+1xU\x0a\xf1b~\xb4\x90\x18y\xe4\x90\xc4m\xd1\
+\xba\xb9\x8a\xbf\xee\xa0\xdd\x16\xdaS\x10\xb3\x90\xc9\x04V\
+.Ml,\x8f\x8c[\xdc:w\xc5\xaf\xddZ\xf3\xda\
+p\xf2$\xfc\xe0\x0c4\xb6\xf3\xb3\x15\x80\x002\xc0\x10\
+\x90\xf9\x0eLN\xc3\x8f\xb3\xf0H2\x95P\xd9\xb1B\
+*Y\xccb\x0f\xa7\xb1\x87\x92xN\x93V\xadA{\
+u\x83\xf5\x9bkM\xcfmY\xebp\xeeM\xf8\xd5\xdb\
+p\x05\xa8\x03Nd\xde\x11 \x1d\x00\x0cG\xe7=0\
+\xf6$<z\x18\xbe\x9a\x85\xb1\x04dl\x88\xfb\xe0\xb9\
+P_\x87\xca\x87\xf0\xfe\x9f\xe1\xdcR\xa7\xd3\xebt\xde\
+\xf7p\xde\xa4\xf3\x9fQ\xddM\x09\xd2t\xbe\xdb\xc2L\
+\xa4\x839\x15H\x02\x88\xd3\xf9_a\x02\xa3\x1e\xe0\x02\
+M:)\x0f%t\xbc\xd9\xef\xfcN\x00\xd1\xe7\xb1\xc0\
+a\x0aH\x06\x8e\xe3\x81\xde\x06\xfc\x08@\x1bh\x05\x8e\
+\xdd@7\xe04:\xfe\x03\xe7\x9a\x10E\xb3\x99\xaa\x5c\
+\x00\x00\x00\x00IEND\xaeB`\x82\
+"
+
+qt_resource_name = b"\
+\x00\x0d\
+\x0e\xa1\xb1G\
+\x00t\
+\x00e\x00x\x00t\x00-\x00h\x00t\x00m\x00l\x00.\x00p\x00n\x00g\
+\x00\x0b\
+\x0c+\x1f\xc7\
+\x00g\
+\x00o\x00-\x00n\x00e\x00x\x00t\x00.\x00p\x00n\x00g\
+\x00\x0d\
+\x07\x1b{\x87\
+\x00g\
+\x00o\x00-\x00b\x00o\x00t\x00t\x00o\x00m\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\
+\x00\x0e\
+\x0d\x8b9\xe7\
+\x00e\
+\x00d\x00i\x00t\x00-\x00c\x00l\x00e\x00a\x00r\x00.\x00p\x00n\x00g\
+\x00\x10\
+\x05\xcb%G\
+\x00A\
+\x00p\x00p\x00L\x00o\x00g\x00o\x00C\x00o\x00l\x00o\x00r\x00.\x00p\x00n\x00g\
+\x00\x09\
+\x05\x04\xbdG\
+\x00n\
+\x00i\x00n\x00j\x00a\x00.\x00p\x00n\x00g\
+\x00\x10\
+\x0f\xcb\x90g\
+\x00d\
+\x00i\x00a\x00l\x00o\x00g\x00-\x00e\x00r\x00r\x00o\x00r\x00.\x00p\x00n\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x0a\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x01\x14\x00\x00\x00\x00\x00\x01\x00\x00CC\
+\x00\x00\x01\x83\x17\xd5\xbe\xbb\
+\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00+^\
+\x00\x00\x01\x83\x17\xd5\xbe\xbb\
+\x00\x00\x00<\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xaa\
+\x00\x00\x01\x83\x17\xd5\xbe\xb7\
+\x00\x00\x00\x5c\x00\x00\x00\x00\x00\x01\x00\x00\x10\x9d\
+\x00\x00\x01\x83\x17\xd5\xbe\xbb\
+\x00\x00\x00\x82\x00\x00\x00\x00\x00\x01\x00\x00\x18\x89\
+\x00\x00\x01\x83\x17\xd5\xbe\xbb\
+\x00\x00\x00 \x00\x00\x00\x00\x00\x01\x00\x00\x06\xe3\
+\x00\x00\x01\x83\x17\xd5\xbe\xbb\
+\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00$\xc8\
+\x00\x00\x01\x83\x17\xd5\xbe\xb7\
+\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00 \x14\
+\x00\x00\x01\x83\x17\xd5\xbe\xbb\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x83\x17\xd5\xbe\xbb\
+\x00\x00\x01,\x00\x00\x00\x00\x00\x01\x00\x00I\xce\
+\x00\x00\x01\x83\x17\xd5\xbe\xb7\
+"
+
+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/webenginewidgets/simplebrowser/data/simplebrowser.qrc b/examples/webenginewidgets/simplebrowser/data/simplebrowser.qrc
new file mode 100644
index 000000000..eda8e3f3d
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/data/simplebrowser.qrc
@@ -0,0 +1,16 @@
+<RCC>
+ <qresource prefix="/">
+ <file>AppLogoColor.png</file>
+ <file>ninja.png</file>
+ </qresource>
+ <qresource prefix="/">
+ <file alias="dialog-error.png">3rdparty/dialog-error.png</file>
+ <file alias="edit-clear.png">3rdparty/edit-clear.png</file>
+ <file alias="go-bottom.png">3rdparty/go-bottom.png</file>
+ <file alias="go-next.png">3rdparty/go-next.png</file>
+ <file alias="go-previous.png">3rdparty/go-previous.png</file>
+ <file alias="process-stop.png">3rdparty/process-stop.png</file>
+ <file alias="text-html.png">3rdparty/text-html.png</file>
+ <file alias="view-refresh.png">3rdparty/view-refresh.png</file>
+ </qresource>
+</RCC>
diff --git a/examples/webenginewidgets/simplebrowser/doc/simplebrowser.rst b/examples/webenginewidgets/simplebrowser/doc/simplebrowser.rst
index 83dd109c5..abe707670 100644
--- a/examples/webenginewidgets/simplebrowser/doc/simplebrowser.rst
+++ b/examples/webenginewidgets/simplebrowser/doc/simplebrowser.rst
@@ -1,8 +1,177 @@
-Simple Browser Example
-======================
+Simple Browser
+==============
-A simple browser based on Qt WebEngine Widgets.
+Simple Browser demonstrates how to use the Qt WebEngine Widgets classes to
+develop a small Web browser application that contains the following elements:
-.. image:: simplebrowser.png
- :width: 400
+- Menu bar for opening stored pages and managing windows and tabs.
+- Navigation bar for entering a URL and for moving backward and
+ forward in the web page browsing history.
+- Multi-tab area for displaying web content within tabs.
+- Status bar for displaying hovered links.
+- A simple download manager.
+
+The web content can be opened in new tabs or separate windows. HTTP and
+proxy authentication can be used for accessing web pages.
+
+Class Hierarchy
++++++++++++++++
+
+We will implement the following main classes:
+
+- ``Browser`` is a class managing the application windows.
+- ``BrowserWindow`` is a ``QMainWindow`` showing the menu, a navigation
+ bar, ``TabWidget``, and a status bar.
+- ``TabWidget`` is a ``QTabWidget`` and contains one or multiple
+ browser tabs.
+- ``WebView`` is a ``QWebEngineView``, provides a view for ``WebPage``,
+ and is added as a tab in ``TabWidget``.
+- ``WebPage`` is a ``QWebEnginePage`` that represents website content.
+
+Additionally, we will implement some auxiliary classes:
+
+- ``WebPopupWindow`` is a ``QWidget`` for showing popup windows.
+- ``DownloadManagerWidget`` is a ``QWidget`` implementing the downloads
+ list.
+
+Creating the Browser Main Window
+++++++++++++++++++++++++++++++++
+
+This example supports multiple main windows that are owned by a ``Browser``
+object. This class also owns the ``DownloadManagerWidget`` and could be used
+for further functionality, such as bookmarks and history managers.
+
+In ``main.cpp``, we create the first ``BrowserWindow`` instance and add it
+to the ``Browser`` object. If no arguments are passed on the command line,
+we open the Qt Homepage.
+
+To suppress flicker when switching the window to OpenGL rendering, we call
+show after the first browser tab has been added.
+
+Creating Tabs
++++++++++++++
+
+The ``BrowserWindow`` constructor initializes all the necessary user interface
+related objects. The centralWidget of ``BrowserWindow`` contains an instance of
+``TabWidget``. The ``TabWidget`` contains one or several ``WebView`` instances
+as tabs, and delegates it's signals and slots to the currently selected one.
+
+In ``TabWidget.setup_view()``, we make sure that the ``TabWidget`` always
+forwards the signals of the currently selected ``WebView``.
+
+Implementing WebView Functionality
+++++++++++++++++++++++++++++++++++
+
+The class ``WebView`` is derived from ``QWebEngineView`` to support the
+following functionality:
+
+- Displaying error messages in case the render process dies
+- Handling ``createWindow()`` requests
+- Adding custom menu items to context menus
+
+Managing WebWindows
+-------------------
+
+The loaded page might want to create windows of the type
+``QWebEnginePage.WebWindowType``, for example, when a JavaScript program requests
+to open a document in a new window or dialog. This is handled by overriding
+``QWebView.createWindow()``.
+
+In case of ``QWebEnginePage.WebDialog``, we create an instance of a custom
+``WebPopupWindow`` class.
+
+Adding Context Menu Items
+-------------------------
+
+We add a menu item to the context menu, so that users can right-click to have
+an inspector opened in a new window. We override
+``QWebEngineView.contextMenuEvent()`` and use
+``QWebEnginePage.createStandardContextMenu()`` to create a default ``QMenu``
+with a default list of ``QWebEnginePage.WebAction`` actions.
+
+Implementing WebPage and WebView Functionality
++++++++++++++++++++++++++++++++++++++++++++++++
+
+We implement ``WebPage`` as a subclass of ``QWebEnginePage`` and ``WebView`` as
+as subclass of ``QWebEngineView`` to enable HTTP, proxy authentication, as well
+as ignoring SSL certificate errors when accessing web pages.
+
+In all the cases above, we display the appropriate dialog to the user. In
+case of authentication, we need to set the correct credential values on the
+QAuthenticator object.
+
+The ``handleProxyAuthenticationRequired`` signal handler implements the very same
+steps for the authentication of HTTP proxies.
+
+In case of SSL errors, we just need to return a boolean value indicating
+whether the certificate should be ignored.
+
+Opening a Web Page
+++++++++++++++++++
+
+This section describes the workflow for opening a new page. When the user
+enters a URL in the navigation bar and presses Enter, the
+``QLineEdit.:returnPressed()`` signal is emitted and the new URL is then handed
+over to ``TabWidget.set_url()``.
+
+The call is forwarded to the currently selected tab.
+
+The ``set_url()`` method of ``WebView`` just forwards the url to the associated
+``WebPage``, which in turn starts the downloading of the page's content in the
+background.
+
+Implementing Private Browsing
++++++++++++++++++++++++++++++
+
+*Private browsing*, *incognito mode*, or *off-the-record* mode is a feature of
+many browsers where normally persistent data, such as cookies, the HTTP cache,
+or browsing history, is kept only in memory, leaving no trace on disk. In this
+example we will implement private browsing on the window level with tabs in one
+window all in either normal or private mode. Alternatively we could implement
+private browsing on the tab-level, with some tabs in a window in normal mode,
+others in private mode.
+
+Implementing private browsing is quite easy using Qt WebEngine. All one has to
+do is to create a new ``QWebEngineProfile`` and use it in the
+``QWebEnginePage`` instead of the default profile. In the example, this new
+profile is owned by the ``Browser`` object.
+
+The required profile for *private browsing* is created together with its first
+window. The default constructor for ``QWebEngineProfile`` already puts it in
+*off-the-record* mode.
+
+All that is left to do is to pass the appropriate profile down to the
+appropriate ``QWebEnginePage`` objects. The ``Browser`` object will hand to
+each new ``BrowserWindow`` either the global default profile or one shared
+*off-the-record* profile instance.
+
+The ``BrowserWindow`` and ``TabWidget`` objects will then ensure that all
+``QWebEnginePage`` objects contained in a window will use this profile.
+
+Managing Downloads
+++++++++++++++++++
+
+Downloads are associated with a ``QWebEngineProfile``. Whenever a download is
+triggered on a web page the ``QWebEngineProfile.downloadRequested`` signal is
+emitted with a ``QWebEngineDownloadRequest``, which in this example is
+forwarded to ``DownloadManagerWidget.download_requested()``.
+
+This method prompts the user for a file name (with a pre-filled suggestion) and
+starts the download (unless the user cancels the ``Save As`` dialog).
+
+The ``QWebEngineDownloadRequest`` object will periodically emit the
+``QWebEngineDownloadRequest.receivedBytesChanged()`` signal to notify potential
+observers of the download progress and the
+``QWebEngineDownloadRequest.stateChanged()`` signal when the download is
+finished or when an error occurs.
+
+Files and Attributions
+++++++++++++++++++++++
+
+The example uses icons from the `Tango Icon Library`_.
+
+.. image:: simplebrowser.webp
+ :width: 800
:alt: Simple Browser Screenshot
+
+.. _`Tango Icon Library`: http://tango.freedesktop.org/Tango_Icon_Library
diff --git a/examples/webenginewidgets/simplebrowser/doc/simplebrowser.webp b/examples/webenginewidgets/simplebrowser/doc/simplebrowser.webp
new file mode 100644
index 000000000..0edc72c0b
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/doc/simplebrowser.webp
Binary files differ
diff --git a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.py b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.py
new file mode 100644
index 000000000..7096b8b57
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWebEngineCore import QWebEngineDownloadRequest
+from PySide6.QtWidgets import QWidget, QFileDialog
+from PySide6.QtCore import QDir, QFileInfo, Qt
+
+from downloadwidget import DownloadWidget
+from ui_downloadmanagerwidget import Ui_DownloadManagerWidget
+
+
+# Displays a list of downloads.
+class DownloadManagerWidget(QWidget):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._ui = Ui_DownloadManagerWidget()
+ self._num_downloads = 0
+ self._ui.setupUi(self)
+
+ def download_requested(self, download):
+ assert (download and download.state() == QWebEngineDownloadRequest.DownloadRequested)
+
+ proposal_dir = download.downloadDirectory()
+ proposal_name = download.downloadFileName()
+ proposal = QDir(proposal_dir).filePath(proposal_name)
+ path, _ = QFileDialog.getSaveFileName(self, "Save as", proposal)
+ if not path:
+ return
+
+ fi = QFileInfo(path)
+ download.setDownloadDirectory(fi.path())
+ download.setDownloadFileName(fi.fileName())
+ download.accept()
+ self.add(DownloadWidget(download))
+
+ self.show()
+
+ def add(self, downloadWidget):
+ downloadWidget.remove_clicked.connect(self.remove)
+ self._ui.m_itemsLayout.insertWidget(0, downloadWidget, 0, Qt.AlignTop)
+ if self._num_downloads == 0:
+ self._ui.m_zeroItemsLabel.hide()
+ self._num_downloads += 1
+
+ def remove(self, downloadWidget):
+ self._ui.m_itemsLayout.removeWidget(downloadWidget)
+ downloadWidget.deleteLater()
+ self._num_downloads -= 1
+ if self._num_downloads == 0:
+ self._ui.m_zeroItemsLabel.show()
diff --git a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.ui b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.ui
new file mode 100644
index 000000000..b7544ac16
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.ui
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DownloadManagerWidget</class>
+ <widget class="QWidget" name="DownloadManagerWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>212</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Downloads</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">#DownloadManagerWidget {
+ background: palette(button)
+}</string>
+ </property>
+ <layout class="QVBoxLayout" name="m_topLevelLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetNoConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QScrollArea" name="m_scrollArea">
+ <property name="styleSheet">
+ <string notr="true">#m_scrollArea {
+ margin: 2px;
+ border: none;
+}</string>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <widget class="QWidget" name="m_items">
+ <property name="styleSheet">
+ <string notr="true">#m_items {background: palette(mid)}</string>
+ </property>
+ <layout class="QVBoxLayout" name="m_itemsLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="m_zeroItemsLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">color: palette(shadow)</string>
+ </property>
+ <property name="text">
+ <string>No downloads</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.py b/examples/webenginewidgets/simplebrowser/downloadwidget.py
new file mode 100644
index 000000000..3b4973cb8
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/downloadwidget.py
@@ -0,0 +1,109 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from ui_downloadwidget import Ui_DownloadWidget
+
+from PySide6.QtWebEngineCore import QWebEngineDownloadRequest
+from PySide6.QtWidgets import QFrame, QWidget
+from PySide6.QtGui import QIcon
+from PySide6.QtCore import QElapsedTimer, Signal, Slot
+
+
+def with_unit(bytes):
+ if bytes < (1 << 10):
+ return f"{bytes} B"
+ if bytes < (1 << 20):
+ s = bytes / (1 << 10)
+ return f"{int(s)} KiB"
+ if bytes < (1 << 30):
+ s = bytes / (1 << 20)
+ return f"{int(s)} MiB"
+ s = bytes / (1 << 30)
+ return f"{int(s)} GiB"
+
+
+class DownloadWidget(QFrame):
+ """Displays one ongoing or finished download (QWebEngineDownloadRequest)."""
+
+ # This signal is emitted when the user indicates that they want to remove
+ # this download from the downloads list.
+ remove_clicked = Signal(QWidget)
+
+ def __init__(self, download, parent=None):
+ super().__init__(parent)
+ self._download = download
+ self._time_added = QElapsedTimer()
+ self._time_added.start()
+ self._cancel_icon = QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop,
+ QIcon(":process-stop.png"))
+ self._remove_icon = QIcon.fromTheme(QIcon.ThemeIcon.EditClear,
+ QIcon(":edit-clear.png"))
+
+ self._ui = Ui_DownloadWidget()
+ self._ui.setupUi(self)
+ self._ui.m_dstName.setText(self._download.downloadFileName())
+ self._ui.m_srcUrl.setText(self._download.url().toDisplayString())
+
+ self._ui.m_cancelButton.clicked.connect(self._canceled)
+
+ self._download.totalBytesChanged.connect(self.update_widget)
+ self._download.receivedBytesChanged.connect(self.update_widget)
+
+ self._download.stateChanged.connect(self.update_widget)
+
+ self.update_widget()
+
+ @Slot()
+ def _canceled(self):
+ state = self._download.state()
+ if state == QWebEngineDownloadRequest.DownloadInProgress:
+ self._download.cancel()
+ else:
+ self.remove_clicked.emit(self)
+
+ def update_widget(self):
+ total_bytes_v = self._download.totalBytes()
+ total_bytes = with_unit(total_bytes_v)
+ received_bytes_v = self._download.receivedBytes()
+ received_bytes = with_unit(received_bytes_v)
+ elapsed = self._time_added.elapsed()
+ bytes_per_second_v = received_bytes_v / elapsed * 1000 if elapsed else 0
+ bytes_per_second = with_unit(bytes_per_second_v)
+
+ state = self._download.state()
+
+ progress_bar = self._ui.m_progressBar
+ if state == QWebEngineDownloadRequest.DownloadInProgress:
+ if total_bytes_v > 0:
+ progress = round(100 * received_bytes_v / total_bytes_v)
+ progress_bar.setValue(progress)
+ progress_bar.setDisabled(False)
+ fmt = f"%p% - {received_bytes} of {total_bytes} downloaded - {bytes_per_second}/s"
+ progress_bar.setFormat(fmt)
+ else:
+ progress_bar.setValue(0)
+ progress_bar.setDisabled(False)
+ fmt = f"unknown size - {received_bytes} downloaded - {bytes_per_second}/s"
+ progress_bar.setFormat(fmt)
+ elif state == QWebEngineDownloadRequest.DownloadCompleted:
+ progress_bar.setValue(100)
+ progress_bar.setDisabled(True)
+ fmt = f"completed - {received_bytes} downloaded - {bytes_per_second}/s"
+ progress_bar.setFormat(fmt)
+ elif state == QWebEngineDownloadRequest.DownloadCancelled:
+ progress_bar.setValue(0)
+ progress_bar.setDisabled(True)
+ fmt = f"cancelled - {received_bytes} downloaded - {bytes_per_second}/s"
+ progress_bar.setFormat(fmt)
+ elif state == QWebEngineDownloadRequest.DownloadInterrupted:
+ progress_bar.setValue(0)
+ progress_bar.setDisabled(True)
+ fmt = "interrupted: " + self._download.interruptReasonString()
+ progress_bar.setFormat(fmt)
+
+ if state == QWebEngineDownloadRequest.DownloadInProgress:
+ self._ui.m_cancelButton.setIcon(self._cancel_icon)
+ self._ui.m_cancelButton.setToolTip("Stop downloading")
+ else:
+ self._ui.m_cancelButton.setIcon(self._remove_icon)
+ self._ui.m_cancelButton.setToolTip("Remove from list")
diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.ui b/examples/webenginewidgets/simplebrowser/downloadwidget.ui
new file mode 100644
index 000000000..47f621486
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/downloadwidget.ui
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DownloadWidget</class>
+ <widget class="QFrame" name="DownloadWidget">
+ <property name="styleSheet">
+ <string notr="true">#DownloadWidget {
+ background: palette(button);
+ border: 1px solid palette(dark);
+ margin: 0px;
+}</string>
+ </property>
+ <layout class="QGridLayout" name="m_topLevelLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetMinAndMaxSize</enum>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="m_dstName">
+ <property name="styleSheet">
+ <string notr="true">font-weight: bold
+</string>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="m_cancelButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed"/>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QPushButton {
+ margin: 1px;
+ border: none;
+}
+QPushButton:pressed {
+ margin: none;
+ border: 1px solid palette(shadow);
+ background: palette(midlight);
+}</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QLabel" name="m_srcUrl">
+ <property name="maximumSize">
+ <size>
+ <width>350</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QProgressBar" name="m_progressBar">
+ <property name="styleSheet">
+ <string notr="true">font-size: 12px</string>
+ </property>
+ <property name="value">
+ <number>24</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/examples/webenginewidgets/simplebrowser/main.py b/examples/webenginewidgets/simplebrowser/main.py
new file mode 100644
index 000000000..781ec29eb
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/main.py
@@ -0,0 +1,45 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the Qt WebEngineWidgets Simple Browser example from Qt v6.x"""
+
+import sys
+from argparse import ArgumentParser, RawTextHelpFormatter
+
+from PySide6.QtWebEngineCore import QWebEngineProfile, QWebEngineSettings
+from PySide6.QtWidgets import QApplication
+from PySide6.QtGui import QIcon
+from PySide6.QtCore import QCoreApplication, QLoggingCategory, QUrl
+
+from browser import Browser
+
+import data.rc_simplebrowser # noqa: F401
+
+if __name__ == "__main__":
+ parser = ArgumentParser(description="Qt Widgets Web Browser",
+ formatter_class=RawTextHelpFormatter)
+ parser.add_argument("--single-process", "-s", action="store_true",
+ help="Run in single process mode (trouble shooting)")
+ parser.add_argument("url", type=str, nargs="?", help="URL")
+ args = parser.parse_args()
+
+ QCoreApplication.setOrganizationName("QtExamples")
+
+ app_args = sys.argv
+ if args.single_process:
+ app_args.extend(["--webEngineArgs", "--single-process"])
+ app = QApplication(app_args)
+ app.setWindowIcon(QIcon(":AppLogoColor.png"))
+ QLoggingCategory.setFilterRules("qt.webenginecontext.debug=true")
+
+ s = QWebEngineProfile.defaultProfile().settings()
+ s.setAttribute(QWebEngineSettings.PluginsEnabled, True)
+ s.setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True)
+
+ browser = Browser()
+ window = browser.create_hidden_window()
+
+ url = QUrl.fromUserInput(args.url) if args.url else QUrl("https://www.qt.io")
+ window.tab_widget().set_url(url)
+ window.show()
+ sys.exit(app.exec())
diff --git a/examples/webenginewidgets/simplebrowser/passworddialog.ui b/examples/webenginewidgets/simplebrowser/passworddialog.ui
new file mode 100644
index 000000000..bbf5004f5
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/passworddialog.ui
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PasswordDialog</class>
+ <widget class="QDialog" name="PasswordDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>399</width>
+ <height>148</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Authentication Required</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" columnstretch="0,0" columnminimumwidth="0,0">
+ <item row="0" column="0">
+ <widget class="QLabel" name="m_iconLabel">
+ <property name="text">
+ <string>Icon</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="m_infoLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Info</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="userLabel">
+ <property name="text">
+ <string>Username:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="m_userNameLineEdit"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="passwordLabel">
+ <property name="text">
+ <string>Password:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="m_passwordLineEdit">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ <zorder>userLabel</zorder>
+ <zorder>m_userNameLineEdit</zorder>
+ <zorder>passwordLabel</zorder>
+ <zorder>m_passwordLineEdit</zorder>
+ <zorder>buttonBox</zorder>
+ <zorder>m_iconLabel</zorder>
+ <zorder>m_infoLabel</zorder>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PasswordDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PasswordDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/webenginewidgets/simplebrowser/simplebrowser.pyproject b/examples/webenginewidgets/simplebrowser/simplebrowser.pyproject
index 6bc12af6b..eceac291e 100644
--- a/examples/webenginewidgets/simplebrowser/simplebrowser.pyproject
+++ b/examples/webenginewidgets/simplebrowser/simplebrowser.pyproject
@@ -1,3 +1,7 @@
{
- "files": ["simplebrowser.py"]
+ "files": ["main.py", "browser.py", "browserwindow.py", "certificateerrordialog.ui",
+ "data/simplebrowser.qrc", "downloadmanagerwidget.py",
+ "downloadmanagerwidget.ui", "downloadwidget.py",
+ "downloadwidget.ui", "passworddialog.ui", "tabwidget.py",
+ "webpage.py", "webpopupwindow.py", "webview.py"]
}
diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.py b/examples/webenginewidgets/simplebrowser/tabwidget.py
new file mode 100644
index 000000000..bda321ac1
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/tabwidget.py
@@ -0,0 +1,241 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from functools import partial
+
+from PySide6.QtWebEngineCore import (QWebEngineFindTextResult, QWebEnginePage)
+from PySide6.QtWidgets import QLabel, QMenu, QTabBar, QTabWidget
+from PySide6.QtGui import QCursor, QIcon, QKeySequence, QPixmap
+from PySide6.QtCore import QUrl, Qt, Signal, Slot
+
+from webpage import WebPage
+from webview import WebView
+
+
+class TabWidget(QTabWidget):
+ link_hovered = Signal(str)
+ load_progress = Signal(int)
+ title_changed = Signal(str)
+ url_changed = Signal(QUrl)
+ fav_icon_changed = Signal(QIcon)
+ web_action_enabled_changed = Signal(QWebEnginePage.WebAction, bool)
+ dev_tools_requested = Signal(QWebEnginePage)
+ find_text_finished = Signal(QWebEngineFindTextResult)
+
+ def __init__(self, profile, parent):
+ super().__init__(parent)
+ self._profile = profile
+ tab_bar = self.tabBar()
+ tab_bar.setTabsClosable(True)
+ tab_bar.setSelectionBehaviorOnRemove(QTabBar.SelectPreviousTab)
+ tab_bar.setMovable(True)
+ tab_bar.setContextMenuPolicy(Qt.CustomContextMenu)
+ tab_bar.customContextMenuRequested.connect(self.handle_context_menu_requested)
+ tab_bar.tabCloseRequested.connect(self.close_tab)
+ tab_bar.tabBarDoubleClicked.connect(self._tabbar_double_clicked)
+ self.setDocumentMode(True)
+ self.setElideMode(Qt.ElideRight)
+
+ self.currentChanged.connect(self.handle_current_changed)
+
+ if profile.isOffTheRecord():
+ icon = QLabel(self)
+ pixmap = QPixmap(":ninja.png")
+ icon.setPixmap(pixmap.scaledToHeight(tab_bar.height()))
+ w = icon.pixmap().width()
+ self.setStyleSheet(f"QTabWidget.tab-bar {{ left: {w}px; }}")
+
+ @Slot(int)
+ def _tabbar_double_clicked(self, index):
+ if index == -1:
+ self.create_tab()
+
+ def handle_current_changed(self, index):
+ if index != -1:
+ view = self.web_view(index)
+ if view.url():
+ view.setFocus()
+ self.title_changed.emit(view.title())
+ self.load_progress.emit(view.load_progress())
+ self.url_changed.emit(view.url())
+ self.fav_icon_changed.emit(view.fav_icon())
+ e = view.is_web_action_enabled(QWebEnginePage.Back)
+ self.web_action_enabled_changed.emit(QWebEnginePage.Back, e)
+ e = view.is_web_action_enabled(QWebEnginePage.Forward)
+ self.web_action_enabled_changed.emit(QWebEnginePage.Forward, e)
+ e = view.is_web_action_enabled(QWebEnginePage.Stop)
+ self.web_action_enabled_changed.emit(QWebEnginePage.Stop, e)
+ e = view.is_web_action_enabled(QWebEnginePage.Reload)
+ self.web_action_enabled_changed.emit(QWebEnginePage.Reload, e)
+ else:
+ self.title_changed.emit("")
+ self.load_progress.emit(0)
+ self.url_changed.emit(QUrl())
+ self.fav_icon_changed.emit(QIcon())
+ self.web_action_enabled_changed.emit(QWebEnginePage.Back, False)
+ self.web_action_enabled_changed.emit(QWebEnginePage.Forward, False)
+ self.web_action_enabled_changed.emit(QWebEnginePage.Stop, False)
+ self.web_action_enabled_changed.emit(QWebEnginePage.Reload, True)
+
+ def handle_context_menu_requested(self, pos):
+ menu = QMenu()
+ menu.addAction("New &Tab", QKeySequence.AddTab, self.create_tab)
+ index = self.tabBar().tabAt(pos)
+ if index != -1:
+ action = menu.addAction("Clone Tab")
+ action.triggered.connect(partial(self.clone_tab, index))
+ menu.addSeparator()
+ action = menu.addAction("Close Tab")
+ action.setShortcut(QKeySequence.Close)
+ action.triggered.connect(partial(self.close_tab, index))
+ action = menu.addAction("Close Other Tabs")
+ action.triggered.connect(partial(self.close_other_tabs, index))
+ menu.addSeparator()
+ action = menu.addAction("Reload Tab")
+ action.setShortcut(QKeySequence.Refresh)
+ action.triggered.connect(partial(self.reload_tab, index))
+ else:
+ menu.addSeparator()
+
+ menu.addAction("Reload All Tabs", self.reload_all_tabs)
+ menu.exec(QCursor.pos())
+
+ def current_web_view(self):
+ return self.web_view(self.currentIndex())
+
+ def web_view(self, index):
+ return self.widget(index)
+
+ def _title_changed(self, web_view, title):
+ index = self.indexOf(web_view)
+ if index != -1:
+ self.setTabText(index, title)
+ self.setTabToolTip(index, title)
+
+ if self.currentIndex() == index:
+ self.title_changed.emit(title)
+
+ def _url_changed(self, web_view, url):
+ index = self.indexOf(web_view)
+ if index != -1:
+ self.tabBar().setTabData(index, url)
+ if self.currentIndex() == index:
+ self.url_changed.emit(url)
+
+ def _load_progress(self, web_view, progress):
+ if self.currentIndex() == self.indexOf(web_view):
+ self.load_progress.emit(progress)
+
+ def _fav_icon_changed(self, web_view, icon):
+ index = self.indexOf(web_view)
+ if index != -1:
+ self.setTabIcon(index, icon)
+ if self.currentIndex() == index:
+ self.fav_icon_changed.emit(icon)
+
+ def _link_hovered(self, web_view, url):
+ if self.currentIndex() == self.indexOf(web_view):
+ self.link_hovered.emit(url)
+
+ def _webaction_enabled_changed(self, webView, action, enabled):
+ if self.currentIndex() == self.indexOf(webView):
+ self.web_action_enabled_changed.emit(action, enabled)
+
+ def _window_close_requested(self, webView):
+ index = self.indexOf(webView)
+ if webView.page().inspectedPage():
+ self.window().close()
+ elif index >= 0:
+ self.close_tab(index)
+
+ def _find_text_finished(self, webView, result):
+ if self.currentIndex() == self.indexOf(webView):
+ self.find_text_finished.emit(result)
+
+ def setup_view(self, webView):
+ web_page = webView.page()
+ webView.titleChanged.connect(partial(self._title_changed, webView))
+ webView.urlChanged.connect(partial(self._url_changed, webView))
+ webView.loadProgress.connect(partial(self._load_progress, webView))
+ web_page.linkHovered.connect(partial(self._link_hovered, webView))
+ webView.fav_icon_changed.connect(partial(self._fav_icon_changed, webView))
+ webView.web_action_enabled_changed.connect(partial(self._webaction_enabled_changed,
+ webView))
+ web_page.windowCloseRequested.connect(partial(self._window_close_requested,
+ webView))
+ webView.dev_tools_requested.connect(self.dev_tools_requested)
+ web_page.findTextFinished.connect(partial(self._find_text_finished,
+ webView))
+
+ def create_tab(self):
+ web_view = self.create_background_tab()
+ self.setCurrentWidget(web_view)
+ return web_view
+
+ def create_background_tab(self):
+ web_view = WebView()
+ web_page = WebPage(self._profile, web_view)
+ web_view.set_page(web_page)
+ self.setup_view(web_view)
+ index = self.addTab(web_view, "(Untitled)")
+ self.setTabIcon(index, web_view.fav_icon())
+ # Workaround for QTBUG-61770
+ web_view.resize(self.currentWidget().size())
+ web_view.show()
+ return web_view
+
+ def reload_all_tabs(self):
+ for i in range(0, self.count()):
+ self.web_view(i).reload()
+
+ def close_other_tabs(self, index):
+ for i in range(index, self.count() - 1, -1):
+ self.close_tab(i)
+ for i in range(-1, index - 1, -1):
+ self.close_tab(i)
+
+ def close_tab(self, index):
+ view = self.web_view(index)
+ if view:
+ has_focus = view.hasFocus()
+ self.removeTab(index)
+ if has_focus and self.count() > 0:
+ self.current_web_view().setFocus()
+ if self.count() == 0:
+ self.create_tab()
+ view.deleteLater()
+
+ def clone_tab(self, index):
+ view = self.web_view(index)
+ if view:
+ tab = self.create_tab()
+ tab.setUrl(view.url())
+
+ def set_url(self, url):
+ view = self.current_web_view()
+ if view:
+ view.setUrl(url)
+ view.setFocus()
+
+ def trigger_web_page_action(self, action):
+ web_view = self.current_web_view()
+ if web_view:
+ web_view.triggerPageAction(action)
+ web_view.setFocus()
+
+ def next_tab(self):
+ next = self.currentIndex() + 1
+ if next == self.count():
+ next = 0
+ self.setCurrentIndex(next)
+
+ def previous_tab(self):
+ next = self.currentIndex() - 1
+ if next < 0:
+ next = self.count() - 1
+ self.setCurrentIndex(next)
+
+ def reload_tab(self, index):
+ view = self.web_view(index)
+ if view:
+ view.reload()
diff --git a/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py b/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py
new file mode 100644
index 000000000..a963f0ac0
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'certificateerrordialog.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
+ QLabel, QSizePolicy, QSpacerItem, QVBoxLayout,
+ QWidget)
+
+class Ui_CertificateErrorDialog(object):
+ def setupUi(self, CertificateErrorDialog):
+ if not CertificateErrorDialog.objectName():
+ CertificateErrorDialog.setObjectName(u"CertificateErrorDialog")
+ CertificateErrorDialog.resize(370, 141)
+ self.verticalLayout = QVBoxLayout(CertificateErrorDialog)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(20, -1, 20, -1)
+ self.m_iconLabel = QLabel(CertificateErrorDialog)
+ self.m_iconLabel.setObjectName(u"m_iconLabel")
+ self.m_iconLabel.setAlignment(Qt.AlignCenter)
+
+ self.verticalLayout.addWidget(self.m_iconLabel)
+
+ self.m_errorLabel = QLabel(CertificateErrorDialog)
+ self.m_errorLabel.setObjectName(u"m_errorLabel")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.m_errorLabel.sizePolicy().hasHeightForWidth())
+ self.m_errorLabel.setSizePolicy(sizePolicy)
+ self.m_errorLabel.setAlignment(Qt.AlignCenter)
+ self.m_errorLabel.setWordWrap(True)
+
+ self.verticalLayout.addWidget(self.m_errorLabel)
+
+ self.m_infoLabel = QLabel(CertificateErrorDialog)
+ self.m_infoLabel.setObjectName(u"m_infoLabel")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.m_infoLabel.sizePolicy().hasHeightForWidth())
+ self.m_infoLabel.setSizePolicy(sizePolicy1)
+ self.m_infoLabel.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.m_infoLabel.setWordWrap(True)
+
+ self.verticalLayout.addWidget(self.m_infoLabel)
+
+ self.verticalSpacer = QSpacerItem(20, 16, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout.addItem(self.verticalSpacer)
+
+ self.buttonBox = QDialogButtonBox(CertificateErrorDialog)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.No|QDialogButtonBox.Yes)
+
+ self.verticalLayout.addWidget(self.buttonBox)
+
+
+ self.retranslateUi(CertificateErrorDialog)
+ self.buttonBox.accepted.connect(CertificateErrorDialog.accept)
+ self.buttonBox.rejected.connect(CertificateErrorDialog.reject)
+
+ QMetaObject.connectSlotsByName(CertificateErrorDialog)
+ # setupUi
+
+ def retranslateUi(self, CertificateErrorDialog):
+ CertificateErrorDialog.setWindowTitle(QCoreApplication.translate("CertificateErrorDialog", u"Dialog", None))
+ self.m_iconLabel.setText(QCoreApplication.translate("CertificateErrorDialog", u"Icon", None))
+ self.m_errorLabel.setText(QCoreApplication.translate("CertificateErrorDialog", u"Error", None))
+ self.m_infoLabel.setText(QCoreApplication.translate("CertificateErrorDialog", u"If you wish so, you may continue with an unverified certificate. Accepting an unverified certificate mean you may not be connected with the host you tried to connect to.\n"
+"\n"
+"Do you wish to override the security check and continue ? ", None))
+ # retranslateUi
+
diff --git a/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py b/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py
new file mode 100644
index 000000000..f0f61aa75
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'downloadmanagerwidget.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QLabel, QLayout, QScrollArea,
+ QSizePolicy, QVBoxLayout, QWidget)
+
+class Ui_DownloadManagerWidget(object):
+ def setupUi(self, DownloadManagerWidget):
+ if not DownloadManagerWidget.objectName():
+ DownloadManagerWidget.setObjectName(u"DownloadManagerWidget")
+ DownloadManagerWidget.resize(400, 212)
+ DownloadManagerWidget.setStyleSheet(u"#DownloadManagerWidget {\n"
+" background: palette(button)\n"
+"}")
+ self.m_topLevelLayout = QVBoxLayout(DownloadManagerWidget)
+ self.m_topLevelLayout.setObjectName(u"m_topLevelLayout")
+ self.m_topLevelLayout.setSizeConstraint(QLayout.SetNoConstraint)
+ self.m_topLevelLayout.setContentsMargins(0, 0, 0, 0)
+ self.m_scrollArea = QScrollArea(DownloadManagerWidget)
+ self.m_scrollArea.setObjectName(u"m_scrollArea")
+ self.m_scrollArea.setStyleSheet(u"#m_scrollArea {\n"
+" margin: 2px;\n"
+" border: none;\n"
+"}")
+ self.m_scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
+ self.m_scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.m_scrollArea.setWidgetResizable(True)
+ self.m_scrollArea.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignTop)
+ self.m_items = QWidget()
+ self.m_items.setObjectName(u"m_items")
+ self.m_items.setStyleSheet(u"#m_items {background: palette(mid)}")
+ self.m_itemsLayout = QVBoxLayout(self.m_items)
+ self.m_itemsLayout.setSpacing(2)
+ self.m_itemsLayout.setObjectName(u"m_itemsLayout")
+ self.m_itemsLayout.setContentsMargins(3, 3, 3, 3)
+ self.m_zeroItemsLabel = QLabel(self.m_items)
+ self.m_zeroItemsLabel.setObjectName(u"m_zeroItemsLabel")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.m_zeroItemsLabel.sizePolicy().hasHeightForWidth())
+ self.m_zeroItemsLabel.setSizePolicy(sizePolicy)
+ self.m_zeroItemsLabel.setStyleSheet(u"color: palette(shadow)")
+ self.m_zeroItemsLabel.setAlignment(Qt.AlignCenter)
+
+ self.m_itemsLayout.addWidget(self.m_zeroItemsLabel)
+
+ self.m_scrollArea.setWidget(self.m_items)
+
+ self.m_topLevelLayout.addWidget(self.m_scrollArea)
+
+
+ self.retranslateUi(DownloadManagerWidget)
+
+ QMetaObject.connectSlotsByName(DownloadManagerWidget)
+ # setupUi
+
+ def retranslateUi(self, DownloadManagerWidget):
+ DownloadManagerWidget.setWindowTitle(QCoreApplication.translate("DownloadManagerWidget", u"Downloads", None))
+ self.m_zeroItemsLabel.setText(QCoreApplication.translate("DownloadManagerWidget", u"No downloads", None))
+ # retranslateUi
+
diff --git a/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py b/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py
new file mode 100644
index 000000000..58c32fdf8
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'downloadwidget.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QLabel,
+ QLayout, QProgressBar, QPushButton, QSizePolicy,
+ QWidget)
+
+class Ui_DownloadWidget(object):
+ def setupUi(self, DownloadWidget):
+ if not DownloadWidget.objectName():
+ DownloadWidget.setObjectName(u"DownloadWidget")
+ DownloadWidget.setStyleSheet(u"#DownloadWidget {\n"
+" background: palette(button);\n"
+" border: 1px solid palette(dark);\n"
+" margin: 0px;\n"
+"}")
+ self.m_topLevelLayout = QGridLayout(DownloadWidget)
+ self.m_topLevelLayout.setObjectName(u"m_topLevelLayout")
+ self.m_topLevelLayout.setSizeConstraint(QLayout.SetMinAndMaxSize)
+ self.m_dstName = QLabel(DownloadWidget)
+ self.m_dstName.setObjectName(u"m_dstName")
+ self.m_dstName.setStyleSheet(u"font-weight: bold\n"
+"")
+
+ self.m_topLevelLayout.addWidget(self.m_dstName, 0, 0, 1, 1)
+
+ self.m_cancelButton = QPushButton(DownloadWidget)
+ self.m_cancelButton.setObjectName(u"m_cancelButton")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.m_cancelButton.sizePolicy().hasHeightForWidth())
+ self.m_cancelButton.setSizePolicy(sizePolicy)
+ self.m_cancelButton.setStyleSheet(u"QPushButton {\n"
+" margin: 1px;\n"
+" border: none;\n"
+"}\n"
+"QPushButton:pressed {\n"
+" margin: none;\n"
+" border: 1px solid palette(shadow);\n"
+" background: palette(midlight);\n"
+"}")
+ self.m_cancelButton.setFlat(False)
+
+ self.m_topLevelLayout.addWidget(self.m_cancelButton, 0, 1, 1, 1)
+
+ self.m_srcUrl = QLabel(DownloadWidget)
+ self.m_srcUrl.setObjectName(u"m_srcUrl")
+ self.m_srcUrl.setMaximumSize(QSize(350, 16777215))
+ self.m_srcUrl.setStyleSheet(u"")
+
+ self.m_topLevelLayout.addWidget(self.m_srcUrl, 1, 0, 1, 2)
+
+ self.m_progressBar = QProgressBar(DownloadWidget)
+ self.m_progressBar.setObjectName(u"m_progressBar")
+ self.m_progressBar.setStyleSheet(u"font-size: 12px")
+ self.m_progressBar.setValue(24)
+
+ self.m_topLevelLayout.addWidget(self.m_progressBar, 2, 0, 1, 2)
+
+
+ self.retranslateUi(DownloadWidget)
+
+ QMetaObject.connectSlotsByName(DownloadWidget)
+ # setupUi
+
+ def retranslateUi(self, DownloadWidget):
+ self.m_dstName.setText(QCoreApplication.translate("DownloadWidget", u"TextLabel", None))
+ self.m_srcUrl.setText(QCoreApplication.translate("DownloadWidget", u"TextLabel", None))
+ pass
+ # retranslateUi
+
diff --git a/examples/webenginewidgets/simplebrowser/ui_passworddialog.py b/examples/webenginewidgets/simplebrowser/ui_passworddialog.py
new file mode 100644
index 000000000..11e0c4a2e
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/ui_passworddialog.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'passworddialog.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
+ QGridLayout, QLabel, QLineEdit, QSizePolicy,
+ QWidget)
+
+class Ui_PasswordDialog(object):
+ def setupUi(self, PasswordDialog):
+ if not PasswordDialog.objectName():
+ PasswordDialog.setObjectName(u"PasswordDialog")
+ PasswordDialog.resize(399, 148)
+ self.gridLayout = QGridLayout(PasswordDialog)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.m_iconLabel = QLabel(PasswordDialog)
+ self.m_iconLabel.setObjectName(u"m_iconLabel")
+ self.m_iconLabel.setAlignment(Qt.AlignCenter)
+
+ self.gridLayout.addWidget(self.m_iconLabel, 0, 0, 1, 1)
+
+ self.m_infoLabel = QLabel(PasswordDialog)
+ self.m_infoLabel.setObjectName(u"m_infoLabel")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.m_infoLabel.sizePolicy().hasHeightForWidth())
+ self.m_infoLabel.setSizePolicy(sizePolicy)
+ self.m_infoLabel.setWordWrap(True)
+
+ self.gridLayout.addWidget(self.m_infoLabel, 0, 1, 1, 1)
+
+ self.userLabel = QLabel(PasswordDialog)
+ self.userLabel.setObjectName(u"userLabel")
+
+ self.gridLayout.addWidget(self.userLabel, 1, 0, 1, 1)
+
+ self.m_userNameLineEdit = QLineEdit(PasswordDialog)
+ self.m_userNameLineEdit.setObjectName(u"m_userNameLineEdit")
+
+ self.gridLayout.addWidget(self.m_userNameLineEdit, 1, 1, 1, 1)
+
+ self.passwordLabel = QLabel(PasswordDialog)
+ self.passwordLabel.setObjectName(u"passwordLabel")
+
+ self.gridLayout.addWidget(self.passwordLabel, 2, 0, 1, 1)
+
+ self.m_passwordLineEdit = QLineEdit(PasswordDialog)
+ self.m_passwordLineEdit.setObjectName(u"m_passwordLineEdit")
+ self.m_passwordLineEdit.setEchoMode(QLineEdit.Password)
+
+ self.gridLayout.addWidget(self.m_passwordLineEdit, 2, 1, 1, 1)
+
+ self.buttonBox = QDialogButtonBox(PasswordDialog)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2)
+
+ self.userLabel.raise_()
+ self.m_userNameLineEdit.raise_()
+ self.passwordLabel.raise_()
+ self.m_passwordLineEdit.raise_()
+ self.buttonBox.raise_()
+ self.m_iconLabel.raise_()
+ self.m_infoLabel.raise_()
+
+ self.retranslateUi(PasswordDialog)
+ self.buttonBox.accepted.connect(PasswordDialog.accept)
+ self.buttonBox.rejected.connect(PasswordDialog.reject)
+
+ QMetaObject.connectSlotsByName(PasswordDialog)
+ # setupUi
+
+ def retranslateUi(self, PasswordDialog):
+ PasswordDialog.setWindowTitle(QCoreApplication.translate("PasswordDialog", u"Authentication Required", None))
+ self.m_iconLabel.setText(QCoreApplication.translate("PasswordDialog", u"Icon", None))
+ self.m_infoLabel.setText(QCoreApplication.translate("PasswordDialog", u"Info", None))
+ self.userLabel.setText(QCoreApplication.translate("PasswordDialog", u"Username:", None))
+ self.passwordLabel.setText(QCoreApplication.translate("PasswordDialog", u"Password:", None))
+ # retranslateUi
+
diff --git a/examples/webenginewidgets/simplebrowser/webpage.py b/examples/webenginewidgets/simplebrowser/webpage.py
new file mode 100644
index 000000000..2f2800a17
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/webpage.py
@@ -0,0 +1,29 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from functools import partial
+
+from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineCertificateError
+from PySide6.QtCore import QTimer, Signal
+
+
+class WebPage(QWebEnginePage):
+
+ create_certificate_error_dialog = Signal(QWebEngineCertificateError)
+
+ def __init__(self, profile, parent):
+ super().__init__(profile, parent)
+
+ self.selectClientCertificate.connect(self.handle_select_client_certificate)
+ self.certificateError.connect(self.handle_certificate_error)
+
+ def _emit_create_certificate_error_dialog(self, error):
+ self.create_certificate_error_dialog.emit(error)
+
+ def handle_certificate_error(self, error):
+ error.defer()
+ QTimer.singleShot(0, partial(self._emit_create_certificate_error_dialog, error))
+
+ def handle_select_client_certificate(self, selection):
+ # Just select one.
+ selection.select(selection.certificates()[0])
diff --git a/examples/webenginewidgets/simplebrowser/webpopupwindow.py b/examples/webenginewidgets/simplebrowser/webpopupwindow.py
new file mode 100644
index 000000000..fac27a61a
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/webpopupwindow.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWidgets import QLineEdit, QSizePolicy, QWidget, QVBoxLayout
+from PySide6.QtGui import QAction
+from PySide6.QtCore import QUrl, Qt, Slot
+
+from webpage import WebPage
+
+
+class WebPopupWindow(QWidget):
+
+ def __init__(self, view, profile, parent=None):
+ super().__init__(parent, Qt.Window)
+ self.m_urlLineEdit = QLineEdit(self)
+ self._url_line_edit = QLineEdit()
+ self._fav_action = QAction(self)
+ self._view = view
+
+ self.setAttribute(Qt.WA_DeleteOnClose)
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+
+ layout = QVBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.addWidget(self._url_line_edit)
+ layout.addWidget(self._view)
+
+ self._view.setPage(WebPage(profile, self._view))
+ self._view.setFocus()
+
+ self._url_line_edit.setReadOnly(True)
+ self._url_line_edit.addAction(self._fav_action, QLineEdit.LeadingPosition)
+
+ self._view.titleChanged.connect(self.setWindowTitle)
+ self._view.urlChanged.connect(self._url_changed)
+ self._view.fav_icon_changed.connect(self._fav_action.setIcon)
+ p = self._view.page()
+ p.geometryChangeRequested.connect(self.handle_geometry_change_requested)
+ p.windowCloseRequested.connect(self.close)
+
+ @Slot(QUrl)
+ def _url_changed(self, url):
+ self._url_line_edit.setText(url.toDisplayString())
+
+ def view(self):
+ return self._view
+
+ def handle_geometry_change_requested(self, newGeometry):
+ window = self.windowHandle()
+ if window:
+ self.setGeometry(newGeometry.marginsRemoved(window.frameMargins()))
+ self.show()
+ self._view.setFocus()
diff --git a/examples/webenginewidgets/simplebrowser/webview.py b/examples/webenginewidgets/simplebrowser/webview.py
new file mode 100644
index 000000000..e1282c1dd
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/webview.py
@@ -0,0 +1,294 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from functools import partial
+
+from PySide6.QtWebEngineCore import (QWebEngineFileSystemAccessRequest,
+ QWebEnginePage)
+from PySide6.QtWebEngineWidgets import QWebEngineView
+
+from PySide6.QtWidgets import QDialog, QMessageBox, QStyle
+from PySide6.QtGui import QIcon
+from PySide6.QtNetwork import QAuthenticator
+from PySide6.QtCore import QTimer, Signal, Slot
+
+from webpage import WebPage
+from webpopupwindow import WebPopupWindow
+from ui_passworddialog import Ui_PasswordDialog
+from ui_certificateerrordialog import Ui_CertificateErrorDialog
+
+
+def question_for_feature(feature):
+ if feature == QWebEnginePage.Geolocation:
+ return "Allow %1 to access your location information?"
+ if feature == QWebEnginePage.MediaAudioCapture:
+ return "Allow %1 to access your microphone?"
+ if feature == QWebEnginePage.MediaVideoCapture:
+ return "Allow %1 to access your webcam?"
+ if feature == QWebEnginePage.MediaAudioVideoCapture:
+ return "Allow %1 to access your microphone and webcam?"
+ if feature == QWebEnginePage.MouseLock:
+ return "Allow %1 to lock your mouse cursor?"
+ if feature == QWebEnginePage.DesktopVideoCapture:
+ return "Allow %1 to capture video of your desktop?"
+ if feature == QWebEnginePage.DesktopAudioVideoCapture:
+ return "Allow %1 to capture audio and video of your desktop?"
+ if feature == QWebEnginePage.Notifications:
+ return "Allow %1 to show notification on your desktop?"
+ return ""
+
+
+class WebView(QWebEngineView):
+
+ web_action_enabled_changed = Signal(QWebEnginePage.WebAction, bool)
+ fav_icon_changed = Signal(QIcon)
+ dev_tools_requested = Signal(QWebEnginePage)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self._load_progress = 100
+ self.loadStarted.connect(self._load_started)
+ self.loadProgress.connect(self._slot_load_progress)
+ self.loadFinished.connect(self._load_finished)
+ self.iconChanged.connect(self._emit_faviconchanged)
+ self.renderProcessTerminated.connect(self._render_process_terminated)
+
+ self._error_icon = QIcon(":dialog-error.png")
+ self._loading_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh,
+ QIcon(":view-refresh.png"))
+ self._default_icon = QIcon(":text-html.png")
+
+ @Slot()
+ def _load_started(self):
+ self._load_progress = 0
+ self.fav_icon_changed.emit(self.fav_icon())
+
+ @Slot(int)
+ def _slot_load_progress(self, progress):
+ self._load_progress = progress
+
+ @Slot()
+ def _emit_faviconchanged(self):
+ self.fav_icon_changed.emit(self.fav_icon())
+
+ @Slot(bool)
+ def _load_finished(self, success):
+ self._load_progress = 100 if success else -1
+ self._emit_faviconchanged()
+
+ @Slot(QWebEnginePage.RenderProcessTerminationStatus, int)
+ def _render_process_terminated(self, termStatus, statusCode):
+ status = ""
+ if termStatus == QWebEnginePage.NormalTerminationStatus:
+ status = "Render process normal exit"
+ elif termStatus == QWebEnginePage.AbnormalTerminationStatus:
+ status = "Render process abnormal exit"
+ elif termStatus == QWebEnginePage.CrashedTerminationStatus:
+ status = "Render process crashed"
+ elif termStatus == QWebEnginePage.KilledTerminationStatus:
+ status = "Render process killed"
+
+ m = f"Render process exited with code: {statusCode:#x}\nDo you want to reload the page?"
+ btn = QMessageBox.question(self.window(), status, m)
+ if btn == QMessageBox.Yes:
+ QTimer.singleShot(0, self.reload)
+
+ def set_page(self, page):
+ old_page = self.page()
+ if old_page and isinstance(old_page, WebPage):
+ old_page.createCertificateErrorDialog.disconnect(self.handle_certificate_error)
+ old_page.authenticationRequired.disconnect(self.handle_authentication_required)
+ old_page.featurePermissionRequested.disconnect(self.handle_feature_permission_requested)
+ old_page.proxyAuthenticationRequired.disconnect(
+ self.handle_proxy_authentication_required)
+ old_page.registerProtocolHandlerRequested.disconnect(
+ self.handle_register_protocol_handler_requested)
+ old_page.fileSystemAccessRequested.disconnect(self.handle_file_system_access_requested)
+
+ self.create_web_action_trigger(page, QWebEnginePage.Forward)
+ self.create_web_action_trigger(page, QWebEnginePage.Back)
+ self.create_web_action_trigger(page, QWebEnginePage.Reload)
+ self.create_web_action_trigger(page, QWebEnginePage.Stop)
+ super().setPage(page)
+ page.create_certificate_error_dialog.connect(self.handle_certificate_error)
+ page.authenticationRequired.connect(self.handle_authentication_required)
+ page.featurePermissionRequested.connect(self.handle_feature_permission_requested)
+ page.proxyAuthenticationRequired.connect(self.handle_proxy_authentication_required)
+ page.registerProtocolHandlerRequested.connect(
+ self.handle_register_protocol_handler_requested)
+ page.fileSystemAccessRequested.connect(self.handle_file_system_access_requested)
+
+ def load_progress(self):
+ return self._load_progress
+
+ def _emit_webactionenabledchanged(self, action, webAction):
+ self.web_action_enabled_changed.emit(webAction, action.isEnabled())
+
+ def create_web_action_trigger(self, page, webAction):
+ action = page.action(webAction)
+ action.changed.connect(partial(self._emit_webactionenabledchanged, action, webAction))
+
+ def is_web_action_enabled(self, webAction):
+ return self.page().action(webAction).isEnabled()
+
+ def fav_icon(self):
+ fav_icon = self.icon()
+ if not fav_icon.isNull():
+ return fav_icon
+ if self._load_progress < 0:
+ return self._error_icon
+ if self._load_progress < 100:
+ return self._loading_icon
+ return self._default_icon
+
+ def createWindow(self, type):
+ main_window = self.window()
+ if not main_window:
+ return None
+
+ if type == QWebEnginePage.WebBrowserTab:
+ return main_window.tab_widget().create_tab()
+
+ if type == QWebEnginePage.WebBrowserBackgroundTab:
+ return main_window.tab_widget().create_background_tab()
+
+ if type == QWebEnginePage.WebBrowserWindow:
+ return main_window.browser().createWindow().current_tab()
+
+ if type == QWebEnginePage.WebDialog:
+ view = WebView()
+ WebPopupWindow(view, self.page().profile(), self.window())
+ view.dev_tools_requested.connect(self.dev_tools_requested)
+ return view
+
+ return None
+
+ @Slot()
+ def _emit_devtools_requested(self):
+ self.dev_tools_requested.emit(self.page())
+
+ def contextMenuEvent(self, event):
+ menu = self.createStandardContextMenu()
+ actions = menu.actions()
+ inspect_action = self.page().action(QWebEnginePage.InspectElement)
+ if inspect_action in actions:
+ inspect_action.setText("Inspect element")
+ else:
+ vs = self.page().action(QWebEnginePage.ViewSource)
+ if vs not in actions:
+ menu.addSeparator()
+
+ action = menu.addAction("Open inspector in new window")
+ action.triggered.connect(self._emit_devtools_requested)
+
+ menu.popup(event.globalPos())
+
+ def handle_certificate_error(self, error):
+ w = self.window()
+ dialog = QDialog(w)
+ dialog.setModal(True)
+
+ certificate_dialog = Ui_CertificateErrorDialog()
+ certificate_dialog.setupUi(dialog)
+ certificate_dialog.m_iconLabel.setText("")
+ icon = QIcon(w.style().standardIcon(QStyle.SP_MessageBoxWarning, 0, w))
+ certificate_dialog.m_iconLabel.setPixmap(icon.pixmap(32, 32))
+ certificate_dialog.m_errorLabel.setText(error.description())
+ dialog.setWindowTitle("Certificate Error")
+
+ if dialog.exec() == QDialog.Accepted:
+ error.acceptCertificate()
+ else:
+ error.rejectCertificate()
+
+ def handle_authentication_required(self, requestUrl, auth):
+ w = self.window()
+ dialog = QDialog(w)
+ dialog.setModal(True)
+
+ password_dialog = Ui_PasswordDialog()
+ password_dialog.setupUi(dialog)
+
+ password_dialog.m_iconLabel.setText("")
+ icon = QIcon(w.style().standardIcon(QStyle.SP_MessageBoxQuestion, 0, w))
+ password_dialog.m_iconLabel.setPixmap(icon.pixmap(32, 32))
+
+ url_str = requestUrl.toString().toHtmlEscaped()
+ realm = auth.realm()
+ m = f'Enter username and password for "{realm}" at {url_str}'
+ password_dialog.m_infoLabel.setText(m)
+ password_dialog.m_infoLabel.setWordWrap(True)
+
+ if dialog.exec() == QDialog.Accepted:
+ auth.setUser(password_dialog.m_userNameLineEdit.text())
+ auth.setPassword(password_dialog.m_passwordLineEdit.text())
+ else:
+ # Set authenticator null if dialog is cancelled
+ auth = QAuthenticator()
+
+ def handle_feature_permission_requested(self, securityOrigin, feature):
+ title = "Permission Request"
+ host = securityOrigin.host()
+ question = question_for_feature(feature).replace("%1", host)
+ w = self.window()
+ page = self.page()
+ if question and QMessageBox.question(w, title, question) == QMessageBox.Yes:
+ page.setFeaturePermission(securityOrigin, feature,
+ QWebEnginePage.PermissionGrantedByUser)
+ else:
+ page.setFeaturePermission(securityOrigin, feature,
+ QWebEnginePage.PermissionDeniedByUser)
+
+ def handle_proxy_authentication_required(self, url, auth, proxyHost):
+ w = self.window()
+ dialog = QDialog(w)
+ dialog.setModal(True)
+
+ password_dialog = Ui_PasswordDialog()
+ password_dialog.setupUi(dialog)
+
+ password_dialog.m_iconLabel.setText("")
+
+ icon = QIcon(w.style().standardIcon(QStyle.SP_MessageBoxQuestion, 0, w))
+ password_dialog.m_iconLabel.setPixmap(icon.pixmap(32, 32))
+
+ proxy = proxyHost.toHtmlEscaped()
+ password_dialog.m_infoLabel.setText(f'Connect to proxy "{proxy}" using:')
+ password_dialog.m_infoLabel.setWordWrap(True)
+
+ if dialog.exec() == QDialog.Accepted:
+ auth.setUser(password_dialog.m_userNameLineEdit.text())
+ auth.setPassword(password_dialog.m_passwordLineEdit.text())
+ else:
+ # Set authenticator null if dialog is cancelled
+ auth = QAuthenticator()
+
+ def handle_register_protocol_handler_requested(self, request):
+ host = request.origin().host()
+ m = f"Allow {host} to open all {request.scheme()} links?"
+ answer = QMessageBox.question(self.window(), "Permission Request", m)
+ if answer == QMessageBox.Yes:
+ request.accept()
+ else:
+ request.reject()
+
+ def handle_file_system_access_requested(self, request):
+ access_type = ""
+ type = request.accessFlags()
+ if type == QWebEngineFileSystemAccessRequest.Read:
+ access_type = "read"
+ elif type == QWebEngineFileSystemAccessRequest.Write:
+ access_type = "write"
+ elif type == (QWebEngineFileSystemAccessRequest.Read
+ | QWebEngineFileSystemAccessRequest.Write):
+ access_type = "read and write"
+ host = request.origin().host()
+ path = request.filePath().toString()
+ t = "File system access request"
+ m = f"Give {host} {access_type} access to {path}?"
+ answer = QMessageBox.question(self.window(), t, m)
+ if answer == QMessageBox.Yes:
+ request.accept()
+ else:
+ request.reject()
diff --git a/examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py b/examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py
deleted file mode 100644
index 7a76e8a7c..000000000
--- a/examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py
+++ /dev/null
@@ -1,276 +0,0 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-import json
-import os
-import warnings
-
-from PySide6 import QtCore
-from PySide6.QtCore import QDir, QFileInfo, QStandardPaths, Qt, QUrl
-from PySide6.QtGui import QIcon, QStandardItem, QStandardItemModel
-from PySide6.QtWidgets import QMenu, QMessageBox, QTreeView
-
-_url_role = Qt.UserRole + 1
-
-# Default bookmarks as an array of arrays which is the form
-# used to read from/write to a .json bookmarks file
-_default_bookmarks = [
- ['Tool Bar'],
- ['http://qt.io', 'Qt', ':/qt-project.org/qmessagebox/images/qtlogo-64.png'],
- ['https://download.qt.io/snapshots/ci/pyside/', 'Downloads'],
- ['https://doc.qt.io/qtforpython/', 'Documentation'],
- ['https://bugreports.qt.io/projects/PYSIDE/', 'Bug Reports'],
- ['https://www.python.org/', 'Python', None],
- ['https://wiki.qt.io/PySide6', 'Qt for Python', None],
- ['Other Bookmarks']
-]
-
-
-def _config_dir():
- location = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation)
- return f'{location}/QtForPythonBrowser'
-
-
-_bookmark_file = 'bookmarks.json'
-
-
-def _create_folder_item(title):
- result = QStandardItem(title)
- result.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
- return result
-
-
-def _create_item(url, title, icon):
- result = QStandardItem(title)
- result.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
- result.setData(url, _url_role)
- if icon is not None:
- result.setIcon(icon)
- return result
-
-
-# Create the model from an array of arrays
-def _create_model(parent, serialized_bookmarks):
- result = QStandardItemModel(0, 1, parent)
- last_folder_item = None
- for entry in serialized_bookmarks:
- if len(entry) == 1:
- last_folder_item = _create_folder_item(entry[0])
- result.appendRow(last_folder_item)
- else:
- url = QUrl.fromUserInput(entry[0])
- title = entry[1]
- icon = QIcon(entry[2]) if len(entry) > 2 and entry[2] else None
- last_folder_item.appendRow(_create_item(url, title, icon))
- return result
-
-
-# Serialize model into an array of arrays, writing out the icons
-# into .png files under directory in the process
-def _serialize_model(model, directory):
- result = []
- folder_count = model.rowCount()
- for f in range(0, folder_count):
- folder_item = model.item(f)
- result.append([folder_item.text()])
- item_count = folder_item.rowCount()
- for i in range(0, item_count):
- item = folder_item.child(i)
- entry = [item.data(_url_role).toString(), item.text()]
- icon = item.icon()
- if not icon.isNull():
- icon_sizes = icon.availableSizes()
- largest_size = icon_sizes[len(icon_sizes) - 1]
- w = largest_size.width()
- icon_file_name = f'{directory}/icon{f:02}_{i:02}_{w}.png'
- icon.pixmap(largest_size).save(icon_file_name, 'PNG')
- entry.append(icon_file_name)
- result.append(entry)
- return result
-
-
-# Bookmarks as a tree view to be used in a dock widget with
-# functionality to persist and populate tool bars and menus.
-class BookmarkWidget(QTreeView):
- """Provides a tree view to manage the bookmarks."""
-
- open_bookmark = QtCore.Signal(QUrl)
- open_bookmark_in_new_tab = QtCore.Signal(QUrl)
- changed = QtCore.Signal()
-
- def __init__(self):
- super().__init__()
- self.setRootIsDecorated(False)
- self.setUniformRowHeights(True)
- self.setHeaderHidden(True)
- self._model = _create_model(self, self._read_bookmarks())
- self.setModel(self._model)
- self.expandAll()
- self.activated.connect(self._activated)
- self._model.rowsInserted.connect(self._changed)
- self._model.rowsRemoved.connect(self._changed)
- self._model.dataChanged.connect(self._changed)
- self._modified = False
-
- def _changed(self):
- self._modified = True
- self.changed.emit()
-
- def _activated(self, index):
- item = self._model.itemFromIndex(index)
- self.open_bookmark.emit(item.data(_url_role))
-
- def _action_activated(self, index):
- action = self.sender()
- self.open_bookmark.emit(action.data())
-
- def _tool_bar_item(self):
- return self._model.item(0, 0)
-
- def _other_item(self):
- return self._model.item(1, 0)
-
- def add_bookmark(self, url, title, icon):
- self._other_item().appendRow(_create_item(url, title, icon))
-
- def add_tool_bar_bookmark(self, url, title, icon):
- self._tool_bar_item().appendRow(_create_item(url, title, icon))
-
- # Synchronize the bookmarks under parent_item to a target_object
- # like QMenu/QToolBar, which has a list of actions. Update
- # the existing actions, append new ones if needed or hide
- # superfluous ones
- def _populate_actions(self, parent_item, target_object, first_action):
- existing_actions = target_object.actions()
- existing_action_count = len(existing_actions)
- a = first_action
- row_count = parent_item.rowCount()
- for r in range(0, row_count):
- item = parent_item.child(r)
- title = item.text()
- icon = item.icon()
- url = item.data(_url_role)
- if a < existing_action_count:
- action = existing_actions[a]
- if (title != action.toolTip()):
- action.setText(BookmarkWidget.short_title(title))
- action.setIcon(icon)
- action.setToolTip(title)
- action.setData(url)
- action.setVisible(True)
- else:
- short_title = BookmarkWidget.short_title(title)
- action = target_object.addAction(icon, short_title)
- action.setToolTip(title)
- action.setData(url)
- action.triggered.connect(self._action_activated)
- a = a + 1
- while a < existing_action_count:
- existing_actions[a].setVisible(False)
- a = a + 1
-
- def populate_tool_bar(self, tool_bar):
- self._populate_actions(self._tool_bar_item(), tool_bar, 0)
-
- def populate_other(self, menu, first_action):
- self._populate_actions(self._other_item(), menu, first_action)
-
- def _current_item(self):
- index = self.currentIndex()
- if index.isValid():
- item = self._model.itemFromIndex(index)
- if item.parent(): # exclude top level items
- return item
- return None
-
- def context_menu_event(self, event):
- context_menu = QMenu()
- open_in_new_tab_action = context_menu.addAction("Open in New Tab")
- remove_action = context_menu.addAction("Remove...")
- current_item = self._current_item()
- open_in_new_tab_action.setEnabled(current_item is not None)
- remove_action.setEnabled(current_item is not None)
- chosen_action = context_menu.exec(event.globalPos())
- if chosen_action == open_in_new_tab_action:
- self.open_bookmarkInNewTab.emit(current_item.data(_url_role))
- elif chosen_action == remove_action:
- self._remove_item(current_item)
-
- def _remove_item(self, item):
- message = f"Would you like to remove \"{item.text()}\"?"
- button = QMessageBox.question(self, "Remove", message,
- QMessageBox.Yes | QMessageBox.No)
- if button == QMessageBox.Yes:
- item.parent().removeRow(item.row())
-
- def write_bookmarks(self):
- if not self._modified:
- return
- dir_path = _config_dir()
- native_dir_path = QDir.toNativeSeparators(dir_path)
- directory = QFileInfo(dir_path)
- if not directory.isDir():
- print(f'Creating {native_dir_path}...')
- if not QDir(directory.absolutePath()).mkpath(directory.fileName()):
- warnings.warn(f'Cannot create {native_dir_path}.',
- RuntimeWarning)
- return
- serialized_model = _serialize_model(self._model, dir_path)
- bookmark_file_name = os.path.join(native_dir_path, _bookmark_file)
- print(f'Writing {bookmark_file_name}...')
- with open(bookmark_file_name, 'w') as bookmark_file:
- json.dump(serialized_model, bookmark_file, indent=4)
-
- def _read_bookmarks(self):
- bookmark_file_name = os.path.join(QDir.toNativeSeparators(_config_dir()),
- _bookmark_file)
- if os.path.exists(bookmark_file_name):
- print(f'Reading {bookmark_file_name}...')
- return json.load(open(bookmark_file_name))
- return _default_bookmarks
-
- # Return a short title for a bookmark action,
- # "Qt | Cross Platform.." -> "Qt"
- @staticmethod
- def short_title(t):
- i = t.find(' | ')
- if i == -1:
- i = t.find(' - ')
- return t[0:i] if i != -1 else t
diff --git a/examples/webenginewidgets/tabbedbrowser/browsertabwidget.py b/examples/webenginewidgets/tabbedbrowser/browsertabwidget.py
deleted file mode 100644
index 5e87da074..000000000
--- a/examples/webenginewidgets/tabbedbrowser/browsertabwidget.py
+++ /dev/null
@@ -1,244 +0,0 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from functools import partial
-
-from bookmarkwidget import BookmarkWidget
-from webengineview import WebEngineView
-from historywindow import HistoryWindow
-from PySide6 import QtCore
-from PySide6.QtCore import Qt, QUrl
-from PySide6.QtWidgets import QMenu, QTabBar, QTabWidget
-from PySide6.QtWebEngineCore import QWebEngineDownloadRequest, QWebEnginePage
-
-
-class BrowserTabWidget(QTabWidget):
- """Enables having several tabs with QWebEngineView."""
-
- url_changed = QtCore.Signal(QUrl)
- enabled_changed = QtCore.Signal(QWebEnginePage.WebAction, bool)
- download_requested = QtCore.Signal(QWebEngineDownloadRequest)
-
- def __init__(self, window_factory_function):
- super().__init__()
- self.setTabsClosable(True)
- self._window_factory_function = window_factory_function
- self._webengineviews = []
- self._history_windows = {} # map WebengineView to HistoryWindow
- self.currentChanged.connect(self._current_changed)
- self.tabCloseRequested.connect(self.handle_tab_close_request)
- self._actions_enabled = {}
- for web_action in WebEngineView.web_actions():
- self._actions_enabled[web_action] = False
-
- tab_bar = self.tabBar()
- tab_bar.setSelectionBehaviorOnRemove(QTabBar.SelectPreviousTab)
- tab_bar.setContextMenuPolicy(Qt.CustomContextMenu)
- tab_bar.customContextMenuRequested.connect(self._handle_tab_context_menu)
-
- def add_browser_tab(self):
- factory_func = partial(BrowserTabWidget.add_browser_tab, self)
- web_engine_view = WebEngineView(factory_func,
- self._window_factory_function)
- index = self.count()
- self._webengineviews.append(web_engine_view)
- title = f'Tab {index + 1}'
- self.addTab(web_engine_view, title)
- page = web_engine_view.page()
- page.titleChanged.connect(self._title_changed)
- page.iconChanged.connect(self._icon_changed)
- page.profile().downloadRequested.connect(self._download_requested)
- web_engine_view.urlChanged.connect(self._url_changed)
- web_engine_view.enabled_changed.connect(self._enabled_changed)
- self.setCurrentIndex(index)
- return web_engine_view
-
- def load(self, url):
- index = self.currentIndex()
- if index >= 0 and url.isValid():
- self._webengineviews[index].setUrl(url)
-
- def find(self, needle, flags):
- index = self.currentIndex()
- if index >= 0:
- self._webengineviews[index].page().findText(needle, flags)
-
- def url(self):
- index = self.currentIndex()
- return self._webengineviews[index].url() if index >= 0 else QUrl()
-
- def _url_changed(self, url):
- index = self.currentIndex()
- if index >= 0 and self._webengineviews[index] == self.sender():
- self.url_changed.emit(url)
-
- def _title_changed(self, title):
- index = self._index_of_page(self.sender())
- if (index >= 0):
- self.setTabText(index, BookmarkWidget.short_title(title))
-
- def _icon_changed(self, icon):
- index = self._index_of_page(self.sender())
- if (index >= 0):
- self.setTabIcon(index, icon)
-
- def _enabled_changed(self, web_action, enabled):
- index = self.currentIndex()
- if index >= 0 and self._webengineviews[index] == self.sender():
- self._check_emit_enabled_changed(web_action, enabled)
-
- def _check_emit_enabled_changed(self, web_action, enabled):
- if enabled != self._actions_enabled[web_action]:
- self._actions_enabled[web_action] = enabled
- self.enabled_changed.emit(web_action, enabled)
-
- def _current_changed(self, index):
- self._update_actions(index)
- self.url_changed.emit(self.url())
-
- def _update_actions(self, index):
- if index >= 0 and index < len(self._webengineviews):
- view = self._webengineviews[index]
- for web_action in WebEngineView.web_actions():
- enabled = view.is_web_action_enabled(web_action)
- self._check_emit_enabled_changed(web_action, enabled)
-
- def back(self):
- self._trigger_action(QWebEnginePage.Back)
-
- def forward(self):
- self._trigger_action(QWebEnginePage.Forward)
-
- def reload(self):
- self._trigger_action(QWebEnginePage.Reload)
-
- def undo(self):
- self._trigger_action(QWebEnginePage.Undo)
-
- def redo(self):
- self._trigger_action(QWebEnginePage.Redo)
-
- def cut(self):
- self._trigger_action(QWebEnginePage.Cut)
-
- def copy(self):
- self._trigger_action(QWebEnginePage.Copy)
-
- def paste(self):
- self._trigger_action(QWebEnginePage.Paste)
-
- def select_all(self):
- self._trigger_action(QWebEnginePage.SelectAll)
-
- def show_history(self):
- index = self.currentIndex()
- if index >= 0:
- webengineview = self._webengineviews[index]
- history_window = self._history_windows.get(webengineview)
- if not history_window:
- history = webengineview.page().history()
- history_window = HistoryWindow(history, self)
- history_window.open_url.connect(self.load)
- history_window.setWindowFlags(history_window.windowFlags()
- | Qt.Window)
- history_window.setWindowTitle('History')
- self._history_windows[webengineview] = history_window
- else:
- history_window.refresh()
- history_window.show()
- history_window.raise_()
-
- def zoom_factor(self):
- return self._webengineviews[0].zoomFactor() if self._webengineviews else 1.0
-
- def set_zoom_factor(self, z):
- for w in self._webengineviews:
- w.setZoomFactor(z)
-
- def _handle_tab_context_menu(self, point):
- index = self.tabBar().tabAt(point)
- if index < 0:
- return
- tab_count = len(self._webengineviews)
- context_menu = QMenu()
- duplicate_tab_action = context_menu.addAction("Duplicate Tab")
- close_other_tabs_action = context_menu.addAction("Close Other Tabs")
- close_other_tabs_action.setEnabled(tab_count > 1)
- close_tabs_to_the_right_action = context_menu.addAction("Close Tabs to the Right")
- close_tabs_to_the_right_action.setEnabled(index < tab_count - 1)
- close_tab_action = context_menu.addAction("&Close Tab")
- chosen_action = context_menu.exec(self.tabBar().mapToGlobal(point))
- if chosen_action == duplicate_tab_action:
- current_url = self.url()
- self.add_browser_tab().load(current_url)
- elif chosen_action == close_other_tabs_action:
- for t in range(tab_count - 1, -1, -1):
- if t != index:
- self.handle_tab_close_request(t)
- elif chosen_action == close_tabs_to_the_right_action:
- for t in range(tab_count - 1, index, -1):
- self.handle_tab_close_request(t)
- elif chosen_action == close_tab_action:
- self.handle_tab_close_request(index)
-
- def handle_tab_close_request(self, index):
- if (index >= 0 and self.count() > 1):
- webengineview = self._webengineviews[index]
- if self._history_windows.get(webengineview):
- del self._history_windows[webengineview]
- self._webengineviews.remove(webengineview)
- self.removeTab(index)
-
- def close_current_tab(self):
- self.handle_tab_close_request(self.currentIndex())
-
- def _trigger_action(self, action):
- index = self.currentIndex()
- if index >= 0:
- self._webengineviews[index].page().triggerAction(action)
-
- def _index_of_page(self, web_page):
- for p in range(0, len(self._webengineviews)):
- if (self._webengineviews[p].page() == web_page):
- return p
- return -1
-
- def _download_requested(self, item):
- self.download_requested.emit(item)
diff --git a/examples/webenginewidgets/tabbedbrowser/doc/tabbedbrowser.png b/examples/webenginewidgets/tabbedbrowser/doc/tabbedbrowser.png
deleted file mode 100644
index 27c3daa09..000000000
--- a/examples/webenginewidgets/tabbedbrowser/doc/tabbedbrowser.png
+++ /dev/null
Binary files differ
diff --git a/examples/webenginewidgets/tabbedbrowser/doc/tabbedbrowser.rst b/examples/webenginewidgets/tabbedbrowser/doc/tabbedbrowser.rst
deleted file mode 100644
index d8f5deb8d..000000000
--- a/examples/webenginewidgets/tabbedbrowser/doc/tabbedbrowser.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-**********************
-Web Browser Example
-**********************
-
-The example demonstrates the power and simplicity offered by |project| to developers.
-It uses several |pymodname| submodules to offer a fluid and modern-looking UI that
-is apt for a web browser. The application offers the following features:
-
- * Tab-based browsing experience using QTabWidget.
- * Download manager using a QProgressBar and QWebEngineDownloadItem.
- * Bookmark manager using QTreeView.
-
-.. image:: tabbedbrowser.png
-
-The application's code is organized in several parts for ease of maintenance. For example,
-:code:`DownloadWidget` provides a widget to track progress of a download item. In the following
-sections, these different parts are discussed briefly to help you understand the Python code behind
-them a little better.
-
-BookmarkWidget or :code:`bookmarkwidget.py`
-===========================================
-
-This widget docks to the left of the main window by default. It inherits QTreeView and
-loads a default set of bookmarks using a QStandardItemModel. The model is populated at startup
-from a JSON file, which is updated when you add or remove bookmarks from the tree view.
-
-.. automodule:: bookmarkwidget
- :members:
-
-DownloadWidget or :code:`downloadwidget.py`
-=============================================
-
-The widget tracks progress of the download item. It inherits QProgressBar to display
-progress of the QWebEngineDownloadItem instance, and offers a context-menu with actions such as Launch,
-Show in folder, Cancel, and Remove.
-
-.. automodule:: downloadwidget
- :members:
-
-BrowserTabWidget or :code:`browsertabwidget.py`
-===============================================
-
-The widget includes a QWebEngineView to enable viewing web content. It docks to the right
-of BookmarkWidget in the main window.
-
-.. automodule:: browsertabwidget
- :members:
-
-MainWindow or :code:`main.py`
-=============================
-
-This is the parent window that collates all the other widgets together to offer the complete package.
-
-.. automodule:: main
- :members:
-
-
-Try running the example to explore it further.
diff --git a/examples/webenginewidgets/tabbedbrowser/downloadwidget.py b/examples/webenginewidgets/tabbedbrowser/downloadwidget.py
deleted file mode 100644
index aa1479eba..000000000
--- a/examples/webenginewidgets/tabbedbrowser/downloadwidget.py
+++ /dev/null
@@ -1,148 +0,0 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-import sys
-from PySide6 import QtCore
-from PySide6.QtCore import QDir, QFileInfo, QStandardPaths, Qt, QUrl
-from PySide6.QtGui import QDesktopServices
-from PySide6.QtWidgets import QMenu, QProgressBar, QStyleFactory
-from PySide6.QtWebEngineCore import QWebEngineDownloadRequest
-
-
-# A QProgressBar with context menu for displaying downloads in a QStatusBar.
-class DownloadWidget(QProgressBar):
- """Lets you track progress of a QWebEngineDownloadRequest."""
- finished = QtCore.Signal()
- remove_requested = QtCore.Signal()
-
- def __init__(self, download_item):
- super().__init__()
- self._download_item = download_item
- download_item.finished.connect(self._finished)
- download_item.downloadProgress.connect(self._download_progress)
- download_item.stateChanged.connect(self._update_tool_tip())
- path = download_item.path()
- self.setMaximumWidth(300)
- # Shorten 'PySide6-5.11.0a1-5.11.0-cp36-cp36m-linux_x86_64.whl'...
- description = QFileInfo(path).fileName()
- description_length = len(description)
- if description_length > 30:
- description_ini = description[0:10]
- description_end = description[description_length - 10:]
- description = f'{description_ini}...{description_end}'
- self.setFormat(f'{description} %p%')
- self.setOrientation(Qt.Horizontal)
- self.setMinimum(0)
- self.setValue(0)
- self.setMaximum(100)
- self._update_tool_tip()
- # Force progress bar text to be shown on macoS by using 'fusion' style
- if sys.platform == 'darwin':
- self.setStyle(QStyleFactory.create('fusion'))
-
- @staticmethod
- def open_file(file):
- QDesktopServices.openUrl(QUrl.fromLocalFile(file))
-
- @staticmethod
- def open_download_directory():
- path = QStandardPaths.writableLocation(QStandardPaths.DownloadLocation)
- DownloadWidget.open_file(path)
-
- def state(self):
- return self._download_item.state()
-
- def _update_tool_tip(self):
- path = self._download_item.path()
- url_str = self._download_item.url().toString()
- native_sep = QDir.toNativeSeparators(path)
- tool_tip = f"{url_str}\n{native_sep}"
- total_bytes = self._download_item.totalBytes()
- if total_bytes > 0:
- tool_tip += f"\n{total_bytes / 1024}K"
- state = self.state()
- if state == QWebEngineDownloadRequest.DownloadRequested:
- tool_tip += "\n(requested)"
- elif state == QWebEngineDownloadRequest.DownloadInProgress:
- tool_tip += "\n(downloading)"
- elif state == QWebEngineDownloadRequest.DownloadCompleted:
- tool_tip += "\n(completed)"
- elif state == QWebEngineDownloadRequest.DownloadCancelled:
- tool_tip += "\n(cancelled)"
- else:
- tool_tip += "\n(interrupted)"
- self.setToolTip(tool_tip)
-
- def _download_progress(self, bytes_received, bytes_total):
- self.setValue(int(100 * bytes_received / bytes_total))
-
- def _finished(self):
- self._update_tool_tip()
- self.finished.emit()
-
- def _launch(self):
- DownloadWidget.open_file(self._download_item.path())
-
- def mouseDoubleClickEvent(self, event):
- if self.state() == QWebEngineDownloadRequest.DownloadCompleted:
- self._launch()
-
- def contextMenuEvent(self, event):
- state = self.state()
- context_menu = QMenu()
- launch_action = context_menu.addAction("Launch")
- launch_action.setEnabled(state == QWebEngineDownloadRequest.DownloadCompleted)
- show_in_folder_action = context_menu.addAction("Show in Folder")
- show_in_folder_action.setEnabled(state == QWebEngineDownloadRequest.DownloadCompleted)
- cancel_action = context_menu.addAction("Cancel")
- cancel_action.setEnabled(state == QWebEngineDownloadRequest.DownloadInProgress)
- remove_action = context_menu.addAction("Remove")
- remove_action.setEnabled(state != QWebEngineDownloadRequest.DownloadInProgress)
-
- chosen_action = context_menu.exec(event.globalPos())
- if chosen_action == launch_action:
- self._launch()
- elif chosen_action == show_in_folder_action:
- path = QFileInfo(self._download_item.path()).absolutePath()
- DownloadWidget.open_file(path)
- elif chosen_action == cancel_action:
- self._download_item.cancel()
- elif chosen_action == remove_action:
- self.remove_requested.emit()
diff --git a/examples/webenginewidgets/tabbedbrowser/findtoolbar.py b/examples/webenginewidgets/tabbedbrowser/findtoolbar.py
deleted file mode 100644
index c38f01afa..000000000
--- a/examples/webenginewidgets/tabbedbrowser/findtoolbar.py
+++ /dev/null
@@ -1,99 +0,0 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide6 import QtCore
-from PySide6.QtCore import Qt
-from PySide6.QtGui import QIcon, QKeySequence
-from PySide6.QtWidgets import QCheckBox, QLineEdit, QToolBar, QToolButton
-from PySide6.QtWebEngineCore import QWebEnginePage
-
-
-# A Find tool bar (bottom area)
-class FindToolBar(QToolBar):
-
- find = QtCore.Signal(str, QWebEnginePage.FindFlags)
-
- def __init__(self):
- super().__init__()
- self._line_edit = QLineEdit()
- self._line_edit.setClearButtonEnabled(True)
- self._line_edit.setPlaceholderText("Find...")
- self._line_edit.setMaximumWidth(300)
- self._line_edit.returnPressed.connect(self._find_next)
- self.addWidget(self._line_edit)
-
- self._previous_button = QToolButton()
- style_icons = ':/qt-project.org/styles/commonstyle/images/'
- self._previous_button.setIcon(QIcon(style_icons + 'up-32.png'))
- self._previous_button.clicked.connect(self._find_previous)
- self.addWidget(self._previous_button)
-
- self._next_button = QToolButton()
- self._next_button.setIcon(QIcon(style_icons + 'down-32.png'))
- self._next_button.clicked.connect(self._find_next)
- self.addWidget(self._next_button)
-
- self._case_sensitive_checkbox = QCheckBox('Case Sensitive')
- self.addWidget(self._case_sensitive_checkbox)
-
- self._hideButton = QToolButton()
- self._hideButton.setShortcut(QKeySequence(Qt.Key_Escape))
- self._hideButton.setIcon(QIcon(style_icons + 'closedock-16.png'))
- self._hideButton.clicked.connect(self.hide)
- self.addWidget(self._hideButton)
-
- def focus_find(self):
- self._line_edit.setFocus()
-
- def _emit_find(self, backward):
- needle = self._line_edit.text().strip()
- if needle:
- flags = QWebEnginePage.FindFlags()
- if self._case_sensitive_checkbox.isChecked():
- flags |= QWebEnginePage.FindCaseSensitively
- if backward:
- flags |= QWebEnginePage.FindBackward
- self.find.emit(needle, flags)
-
- def _find_next(self):
- self._emit_find(False)
-
- def _find_previous(self):
- self._emit_find(True)
diff --git a/examples/webenginewidgets/tabbedbrowser/historywindow.py b/examples/webenginewidgets/tabbedbrowser/historywindow.py
deleted file mode 100644
index bc2640e69..000000000
--- a/examples/webenginewidgets/tabbedbrowser/historywindow.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#############################################################################
-##
-## Copyright (C) 2019 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide6.QtWidgets import QApplication, QTreeView
-
-from PySide6.QtCore import Signal, QAbstractTableModel, QModelIndex, Qt, QUrl
-
-
-class HistoryModel(QAbstractTableModel):
-
- def __init__(self, history, parent=None):
- super().__init__(parent)
- self._history = history
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- return 'Title' if section == 0 else 'Url'
- return None
-
- def rowCount(self, index=QModelIndex()):
- return self._history.count()
-
- def columnCount(self, index=QModelIndex()):
- return 2
-
- def item_at(self, model_index):
- return self._history.itemAt(model_index.row())
-
- def data(self, index, role=Qt.DisplayRole):
- item = self.item_at(index)
- column = index.column()
- if role == Qt.DisplayRole:
- return item.title() if column == 0 else item.url().toString()
- return None
-
- def refresh(self):
- self.beginResetModel()
- self.endResetModel()
-
-
-class HistoryWindow(QTreeView):
-
- open_url = Signal(QUrl)
-
- def __init__(self, history, parent):
- super().__init__(parent)
-
- self._model = HistoryModel(history, self)
- self.setModel(self._model)
- self.activated.connect(self._activated)
-
- screen = QApplication.desktop().screenGeometry(parent)
- self.resize(screen.width() / 3, screen.height() / 3)
- self._adjustSize()
-
- def refresh(self):
- self._model.refresh()
- self._adjustSize()
-
- def _adjustSize(self):
- if (self._model.rowCount() > 0):
- self.resizeColumnToContents(0)
-
- def _activated(self, index):
- item = self._model.item_at(index)
- self.open_url.emit(item.url())
diff --git a/examples/webenginewidgets/tabbedbrowser/main.py b/examples/webenginewidgets/tabbedbrowser/main.py
deleted file mode 100644
index 400a87540..000000000
--- a/examples/webenginewidgets/tabbedbrowser/main.py
+++ /dev/null
@@ -1,395 +0,0 @@
-
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-"""PySide6 WebEngineWidgets Example"""
-
-import sys
-from bookmarkwidget import BookmarkWidget
-from browsertabwidget import BrowserTabWidget
-from downloadwidget import DownloadWidget
-from findtoolbar import FindToolBar
-from webengineview import WebEngineView
-from PySide6 import QtCore
-from PySide6.QtCore import Qt, QUrl
-from PySide6.QtGui import QAction, QKeySequence, QIcon
-from PySide6.QtWidgets import (QApplication, QDockWidget, QLabel,
- QLineEdit, QMainWindow, QToolBar)
-from PySide6.QtWebEngineCore import QWebEngineDownloadRequest, QWebEnginePage
-
-main_windows = []
-
-
-def create_main_window():
- """Creates a MainWindow using 75% of the available screen resolution."""
- main_win = MainWindow()
- main_windows.append(main_win)
- available_geometry = main_win.screen().availableGeometry()
- main_win.resize(available_geometry.width() * 2 / 3,
- available_geometry.height() * 2 / 3)
- main_win.show()
- return main_win
-
-
-def create_main_window_with_browser():
- """Creates a MainWindow with a BrowserTabWidget."""
- main_win = create_main_window()
- return main_win.add_browser_tab()
-
-
-class MainWindow(QMainWindow):
- """Provides the parent window that includes the BookmarkWidget,
- BrowserTabWidget, and a DownloadWidget, to offer the complete
- web browsing experience."""
-
- def __init__(self):
- super().__init__()
-
- self.setWindowTitle('PySide6 tabbed browser Example')
-
- self._tab_widget = BrowserTabWidget(create_main_window_with_browser)
- self._tab_widget.enabled_changed.connect(self._enabled_changed)
- self._tab_widget.download_requested.connect(self._download_requested)
- self.setCentralWidget(self._tab_widget)
- self.connect(self._tab_widget, QtCore.SIGNAL("url_changed(QUrl)"),
- self.url_changed)
-
- self._bookmark_dock = QDockWidget()
- self._bookmark_dock.setWindowTitle('Bookmarks')
- self._bookmark_widget = BookmarkWidget()
- self._bookmark_widget.open_bookmark.connect(self.load_url)
- self._bookmark_widget.open_bookmark_in_new_tab.connect(self.load_url_in_new_tab)
- self._bookmark_dock.setWidget(self._bookmark_widget)
- self.addDockWidget(Qt.LeftDockWidgetArea, self._bookmark_dock)
-
- self._find_tool_bar = None
-
- self._actions = {}
- self._create_menu()
-
- self._tool_bar = QToolBar()
- self.addToolBar(self._tool_bar)
- for action in self._actions.values():
- if not action.icon().isNull():
- self._tool_bar.addAction(action)
-
- self._addres_line_edit = QLineEdit()
- self._addres_line_edit.setClearButtonEnabled(True)
- self._addres_line_edit.returnPressed.connect(self.load)
- self._tool_bar.addWidget(self._addres_line_edit)
- self._zoom_label = QLabel()
- self.statusBar().addPermanentWidget(self._zoom_label)
- self._update_zoom_label()
-
- self._bookmarksToolBar = QToolBar()
- self.addToolBar(Qt.TopToolBarArea, self._bookmarksToolBar)
- self.insertToolBarBreak(self._bookmarksToolBar)
- self._bookmark_widget.changed.connect(self._update_bookmarks)
- self._update_bookmarks()
-
- def _update_bookmarks(self):
- self._bookmark_widget.populate_tool_bar(self._bookmarksToolBar)
- self._bookmark_widget.populate_other(self._bookmark_menu, 3)
-
- def _create_menu(self):
- file_menu = self.menuBar().addMenu("&File")
- exit_action = QAction(QIcon.fromTheme("application-exit"), "E&xit",
- self, shortcut="Ctrl+Q", triggered=qApp.quit)
- file_menu.addAction(exit_action)
-
- navigation_menu = self.menuBar().addMenu("&Navigation")
-
- style_icons = ':/qt-project.org/styles/commonstyle/images/'
- back_action = QAction(QIcon.fromTheme("go-previous",
- QIcon(style_icons + 'left-32.png')),
- "Back", self,
- shortcut=QKeySequence(QKeySequence.Back),
- triggered=self._tab_widget.back)
- self._actions[QWebEnginePage.Back] = back_action
- back_action.setEnabled(False)
- navigation_menu.addAction(back_action)
- forward_action = QAction(QIcon.fromTheme("go-next",
- QIcon(style_icons + 'right-32.png')),
- "Forward", self,
- shortcut=QKeySequence(QKeySequence.Forward),
- triggered=self._tab_widget.forward)
- forward_action.setEnabled(False)
- self._actions[QWebEnginePage.Forward] = forward_action
-
- navigation_menu.addAction(forward_action)
- reload_action = QAction(QIcon(style_icons + 'refresh-32.png'),
- "Reload", self,
- shortcut=QKeySequence(QKeySequence.Refresh),
- triggered=self._tab_widget.reload)
- self._actions[QWebEnginePage.Reload] = reload_action
- reload_action.setEnabled(False)
- navigation_menu.addAction(reload_action)
-
- navigation_menu.addSeparator()
-
- new_tab_action = QAction("New Tab", self,
- shortcut='Ctrl+T',
- triggered=self.add_browser_tab)
- navigation_menu.addAction(new_tab_action)
-
- close_tab_action = QAction("Close Current Tab", self,
- shortcut="Ctrl+W",
- triggered=self._close_current_tab)
- navigation_menu.addAction(close_tab_action)
-
- navigation_menu.addSeparator()
-
- history_action = QAction("History...", self,
- triggered=self._tab_widget.show_history)
- navigation_menu.addAction(history_action)
-
- edit_menu = self.menuBar().addMenu("&Edit")
-
- find_action = QAction("Find", self,
- shortcut=QKeySequence(QKeySequence.Find),
- triggered=self._show_find)
- edit_menu.addAction(find_action)
-
- edit_menu.addSeparator()
- undo_action = QAction("Undo", self,
- shortcut=QKeySequence(QKeySequence.Undo),
- triggered=self._tab_widget.undo)
- self._actions[QWebEnginePage.Undo] = undo_action
- undo_action.setEnabled(False)
- edit_menu.addAction(undo_action)
-
- redo_action = QAction("Redo", self,
- shortcut=QKeySequence(QKeySequence.Redo),
- triggered=self._tab_widget.redo)
- self._actions[QWebEnginePage.Redo] = redo_action
- redo_action.setEnabled(False)
- edit_menu.addAction(redo_action)
-
- edit_menu.addSeparator()
-
- cut_action = QAction("Cut", self,
- shortcut=QKeySequence(QKeySequence.Cut),
- triggered=self._tab_widget.cut)
- self._actions[QWebEnginePage.Cut] = cut_action
- cut_action.setEnabled(False)
- edit_menu.addAction(cut_action)
-
- copy_action = QAction("Copy", self,
- shortcut=QKeySequence(QKeySequence.Copy),
- triggered=self._tab_widget.copy)
- self._actions[QWebEnginePage.Copy] = copy_action
- copy_action.setEnabled(False)
- edit_menu.addAction(copy_action)
-
- paste_action = QAction("Paste", self,
- shortcut=QKeySequence(QKeySequence.Paste),
- triggered=self._tab_widget.paste)
- self._actions[QWebEnginePage.Paste] = paste_action
- paste_action.setEnabled(False)
- edit_menu.addAction(paste_action)
-
- edit_menu.addSeparator()
-
- select_all_action = QAction("Select All", self,
- shortcut=QKeySequence(QKeySequence.SelectAll),
- triggered=self._tab_widget.select_all)
- self._actions[QWebEnginePage.SelectAll] = select_all_action
- select_all_action.setEnabled(False)
- edit_menu.addAction(select_all_action)
-
- self._bookmark_menu = self.menuBar().addMenu("&Bookmarks")
- add_bookmark_action = QAction("&Add Bookmark", self,
- triggered=self._add_bookmark)
- self._bookmark_menu.addAction(add_bookmark_action)
- add_tool_bar_bookmark_action = QAction("&Add Bookmark to Tool Bar", self,
- triggered=self._add_tool_bar_bookmark)
- self._bookmark_menu.addAction(add_tool_bar_bookmark_action)
- self._bookmark_menu.addSeparator()
-
- tools_menu = self.menuBar().addMenu("&Tools")
- download_action = QAction("Open Downloads", self,
- triggered=DownloadWidget.open_download_directory)
- tools_menu.addAction(download_action)
-
- window_menu = self.menuBar().addMenu("&Window")
-
- window_menu.addAction(self._bookmark_dock.toggleViewAction())
-
- window_menu.addSeparator()
-
- zoom_in_action = QAction(QIcon.fromTheme("zoom-in"),
- "Zoom In", self,
- shortcut=QKeySequence(QKeySequence.ZoomIn),
- triggered=self._zoom_in)
- window_menu.addAction(zoom_in_action)
- zoom_out_action = QAction(QIcon.fromTheme("zoom-out"),
- "Zoom Out", self,
- shortcut=QKeySequence(QKeySequence.ZoomOut),
- triggered=self._zoom_out)
- window_menu.addAction(zoom_out_action)
-
- reset_zoom_action = QAction(QIcon.fromTheme("zoom-original"),
- "Reset Zoom", self,
- shortcut="Ctrl+0",
- triggered=self._reset_zoom)
- window_menu.addAction(reset_zoom_action)
-
- about_menu = self.menuBar().addMenu("&About")
- about_action = QAction("About Qt", self,
- shortcut=QKeySequence(QKeySequence.HelpContents),
- triggered=qApp.aboutQt)
- about_menu.addAction(about_action)
-
- def add_browser_tab(self):
- return self._tab_widget.add_browser_tab()
-
- def _close_current_tab(self):
- if self._tab_widget.count() > 1:
- self._tab_widget.close_current_tab()
- else:
- self.close()
-
- def close_event(self, event):
- main_windows.remove(self)
- event.accept()
-
- def load(self):
- url_string = self._addres_line_edit.text().strip()
- if url_string:
- self.load_url_string(url_string)
-
- def load_url_string(self, url_s):
- url = QUrl.fromUserInput(url_s)
- if (url.isValid()):
- self.load_url(url)
-
- def load_url(self, url):
- self._tab_widget.load(url)
-
- def load_url_in_new_tab(self, url):
- self.add_browser_tab().load(url)
-
- def url_changed(self, url):
- self._addres_line_edit.setText(url.toString())
-
- def _enabled_changed(self, web_action, enabled):
- action = self._actions[web_action]
- if action:
- action.setEnabled(enabled)
-
- def _add_bookmark(self):
- index = self._tab_widget.currentIndex()
- if index >= 0:
- url = self._tab_widget.url()
- title = self._tab_widget.tabText(index)
- icon = self._tab_widget.tabIcon(index)
- self._bookmark_widget.add_bookmark(url, title, icon)
-
- def _add_tool_bar_bookmark(self):
- index = self._tab_widget.currentIndex()
- if index >= 0:
- url = self._tab_widget.url()
- title = self._tab_widget.tabText(index)
- icon = self._tab_widget.tabIcon(index)
- self._bookmark_widget.add_tool_bar_bookmark(url, title, icon)
-
- def _zoom_in(self):
- new_zoom = self._tab_widget.zoom_factor() * 1.5
- if (new_zoom <= WebEngineView.maximum_zoom_factor()):
- self._tab_widget.set_zoom_factor(new_zoom)
- self._update_zoom_label()
-
- def _zoom_out(self):
- new_zoom = self._tab_widget.zoom_factor() / 1.5
- if (new_zoom >= WebEngineView.minimum_zoom_factor()):
- self._tab_widget.set_zoom_factor(new_zoom)
- self._update_zoom_label()
-
- def _reset_zoom(self):
- self._tab_widget.set_zoom_factor(1)
- self._update_zoom_label()
-
- def _update_zoom_label(self):
- percent = int(self._tab_widget.zoom_factor() * 100)
- self._zoom_label.setText(f"{percent}%")
-
- def _download_requested(self, item):
- # Remove old downloads before opening a new one
- for old_download in self.statusBar().children():
- if (type(old_download).__name__ == 'DownloadWidget' and
- old_download.state() != QWebEngineDownloadItem.DownloadInProgress):
- self.statusBar().removeWidget(old_download)
- del old_download
-
- item.accept()
- download_widget = DownloadWidget(item)
- download_widget.remove_requested.connect(self._remove_download_requested,
- Qt.QueuedConnection)
- self.statusBar().addWidget(download_widget)
-
- def _remove_download_requested(self):
- download_widget = self.sender()
- self.statusBar().removeWidget(download_widget)
- del download_widget
-
- def _show_find(self):
- if self._find_tool_bar is None:
- self._find_tool_bar = FindToolBar()
- self._find_tool_bar.find.connect(self._tab_widget.find)
- self.addToolBar(Qt.BottomToolBarArea, self._find_tool_bar)
- else:
- self._find_tool_bar.show()
- self._find_tool_bar.focus_find()
-
- def write_bookmarks(self):
- self._bookmark_widget.write_bookmarks()
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
- main_win = create_main_window()
- initial_urls = sys.argv[1:]
- if not initial_urls:
- initial_urls.append('http://qt.io')
- for url in initial_urls:
- main_win.load_url_in_new_tab(QUrl.fromUserInput(url))
- exit_code = app.exec()
- main_win.write_bookmarks()
- sys.exit(exit_code)
diff --git a/examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyproject b/examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyproject
deleted file mode 100644
index 1d26848b0..000000000
--- a/examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyproject
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "files": ["main.py", "bookmarkwidget.py", "browsertabwidget.py",
- "downloadwidget.py", "findtoolbar.py", "historywindow.py",
- "webengineview.py"]
-}
diff --git a/examples/webenginewidgets/tabbedbrowser/webengineview.py b/examples/webenginewidgets/tabbedbrowser/webengineview.py
deleted file mode 100644
index 19a16e8d3..000000000
--- a/examples/webenginewidgets/tabbedbrowser/webengineview.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from PySide6.QtWebEngineCore import QWebEnginePage
-from PySide6.QtWebEngineWidgets import QWebEngineView
-
-from PySide6 import QtCore
-
-_web_actions = [QWebEnginePage.Back, QWebEnginePage.Forward,
- QWebEnginePage.Reload,
- QWebEnginePage.Undo, QWebEnginePage.Redo,
- QWebEnginePage.Cut, QWebEnginePage.Copy,
- QWebEnginePage.Paste, QWebEnginePage.SelectAll]
-
-
-class WebEngineView(QWebEngineView):
-
- enabled_changed = QtCore.Signal(QWebEnginePage.WebAction, bool)
-
- @staticmethod
- def web_actions():
- return _web_actions
-
- @staticmethod
- def minimum_zoom_factor():
- return 0.25
-
- @staticmethod
- def maximum_zoom_factor():
- return 5
-
- def __init__(self, tab_factory_func, window_factory_func):
- super().__init__()
- self._tab_factory_func = tab_factory_func
- self._window_factory_func = window_factory_func
- page = self.page()
- self._actions = {}
- for web_action in WebEngineView.web_actions():
- action = page.action(web_action)
- action.changed.connect(self._enabled_changed)
- self._actions[action] = web_action
-
- def is_web_action_enabled(self, web_action):
- return self.page().action(web_action).isEnabled()
-
- def createWindow(self, window_type):
- if (window_type == QWebEnginePage.WebBrowserTab or
- window_type == QWebEnginePage.WebBrowserBackgroundTab):
- return self._tab_factory_func()
- return self._window_factory_func()
-
- def _enabled_changed(self):
- action = self.sender()
- web_action = self._actions[action]
- self.enabled_changed.emit(web_action, action.isEnabled())
diff --git a/examples/webenginewidgets/simplebrowser/doc/simplebrowser.png b/examples/webenginewidgets/widgetsnanobrowser/doc/widgetsnanobrowser.png
index 3fa5a0046..3fa5a0046 100644
--- a/examples/webenginewidgets/simplebrowser/doc/simplebrowser.png
+++ b/examples/webenginewidgets/widgetsnanobrowser/doc/widgetsnanobrowser.png
Binary files differ
diff --git a/examples/webenginewidgets/widgetsnanobrowser/doc/widgetsnanobrowser.rst b/examples/webenginewidgets/widgetsnanobrowser/doc/widgetsnanobrowser.rst
new file mode 100644
index 000000000..d9358a230
--- /dev/null
+++ b/examples/webenginewidgets/widgetsnanobrowser/doc/widgetsnanobrowser.rst
@@ -0,0 +1,8 @@
+Qt Widgets Nano Browser Example
+===============================
+
+A minimal browser based on Qt WebEngine Widgets.
+
+.. image:: widgetsnanobrowser.png
+ :width: 400
+ :alt: Minimal Browser Screenshot
diff --git a/examples/webenginewidgets/simplebrowser/simplebrowser.py b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py
index 021d5311e..2db865996 100644
--- a/examples/webenginewidgets/simplebrowser/simplebrowser.py
+++ b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py
@@ -1,51 +1,13 @@
-
-#############################################################################
-##
-## Copyright (C) 2017 The Qt Company Ltd.
-## Contact: http://www.qt.io/licensing/
-##
-## This file is part of the Qt for Python examples of the Qt Toolkit.
-##
-## $QT_BEGIN_LICENSE:BSD$
-## You may use this file under the terms of the BSD license as follows:
-##
-## "Redistribution and use in source and binary forms, with or without
-## modification, are permitted provided that the following conditions are
-## met:
-## * Redistributions of source code must retain the above copyright
-## notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright
-## notice, this list of conditions and the following disclaimer in
-## the documentation and/or other materials provided with the
-## distribution.
-## * Neither the name of The Qt Company Ltd nor the names of its
-## contributors may be used to endorse or promote products derived
-## from this software without specific prior written permission.
-##
-##
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
"""PySide6 WebEngineWidgets Example"""
import sys
-from PySide6.QtCore import QUrl
+from PySide6.QtCore import QUrl, Slot
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (QApplication, QLineEdit,
- QMainWindow, QPushButton, QToolBar)
+ QMainWindow, QPushButton, QToolBar)
from PySide6.QtWebEngineCore import QWebEnginePage
from PySide6.QtWebEngineWidgets import QWebEngineView
@@ -80,17 +42,21 @@ class MainWindow(QMainWindow):
self.webEngineView.page().titleChanged.connect(self.setWindowTitle)
self.webEngineView.page().urlChanged.connect(self.urlChanged)
+ @Slot()
def load(self):
url = QUrl.fromUserInput(self.addressLineEdit.text())
if url.isValid():
self.webEngineView.load(url)
+ @Slot()
def back(self):
self.webEngineView.page().triggerAction(QWebEnginePage.Back)
+ @Slot()
def forward(self):
self.webEngineView.page().triggerAction(QWebEnginePage.Forward)
+ @Slot(QUrl)
def urlChanged(self, url):
self.addressLineEdit.setText(url.toString())
@@ -98,7 +64,7 @@ class MainWindow(QMainWindow):
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
- availableGeometry = app.desktop().availableGeometry(mainWin)
+ availableGeometry = mainWin.screen().availableGeometry()
mainWin.resize(availableGeometry.width() * 2 / 3, availableGeometry.height() * 2 / 3)
mainWin.show()
sys.exit(app.exec())
diff --git a/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.pyproject b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.pyproject
new file mode 100644
index 000000000..c054184df
--- /dev/null
+++ b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["widgetsnanobrowser.py"]
+}