diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2023-03-29 11:56:45 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2023-04-05 11:32:26 +0200 |
commit | cb8df7307868011a8a50d15b999cf6e8e57de782 (patch) | |
tree | 13fa6bdb8c52c7d078453a67db34384f4eee822c /examples/webenginewidgets | |
parent | afe276ae9dc45255dade87110284ff7d6371c419 (diff) |
Port the simple widgets browser
Task-number: PYSIDE-2206
Change-Id: I06173fc74d20e3d508a76200e6733e1cfcf6b35a
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'examples/webenginewidgets')
34 files changed, 3771 insertions, 0 deletions
diff --git a/examples/webenginewidgets/simplebrowser/browser.py b/examples/webenginewidgets/simplebrowser/browser.py new file mode 100644 index 000000000..4dc65bfa3 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/browser.py @@ -0,0 +1,68 @@ +# 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..576865742 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/browserwindow.py @@ -0,0 +1,493 @@ +# 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(":process-stop.png") + self._reload_icon = 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) + 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) + self._history_back_action.setIcon(QIcon(":go-previous.png")) + 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) + self._history_forward_action.setIcon(QIcon(":go-next.png")) + 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 Binary files differnew file mode 100644 index 000000000..cdd95bade --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/dialog-error.png diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.png Binary files differnew file mode 100644 index 000000000..5542948bc --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.png diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.png Binary files differnew file mode 100644 index 000000000..bf973fedc --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.png diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.png Binary files differnew file mode 100644 index 000000000..a68e2db77 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.png diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.png Binary files differnew file mode 100644 index 000000000..c37bc0414 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.png diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.png Binary files differnew file mode 100644 index 000000000..e7a8d1722 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.png 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 Binary files differnew file mode 100644 index 000000000..a896697d7 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/text-html.png diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.png Binary files differnew file mode 100644 index 000000000..606ea9eba --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.png diff --git a/examples/webenginewidgets/simplebrowser/data/AppLogoColor.png b/examples/webenginewidgets/simplebrowser/data/AppLogoColor.png Binary files differnew file mode 100644 index 000000000..2a4971782 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/AppLogoColor.png diff --git a/examples/webenginewidgets/simplebrowser/data/ninja.png b/examples/webenginewidgets/simplebrowser/data/ninja.png Binary files differnew file mode 100644 index 000000000..e5d7b6fd7 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/ninja.png 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 new file mode 100644 index 000000000..abe707670 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/doc/simplebrowser.rst @@ -0,0 +1,177 @@ +Simple Browser +============== + +Simple Browser demonstrates how to use the Qt WebEngine Widgets classes to +develop a small Web browser application that contains the following elements: + +- 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 Binary files differnew file mode 100644 index 000000000..0edc72c0b --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/doc/simplebrowser.webp 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..89dc2889a --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.py @@ -0,0 +1,108 @@ +# 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(":process-stop.png") + self._remove_icon = 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..054b8fa0f --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/main.py @@ -0,0 +1,40 @@ +# 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 + +if __name__ == "__main__": + parser = ArgumentParser(description="Qt Widgets Web Browser", + formatter_class=RawTextHelpFormatter) + parser.add_argument("url", type=str, nargs="?", help="URL") + args = parser.parse_args() + + QCoreApplication.setOrganizationName("QtExamples") + + app = QApplication(sys.argv) + 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 new file mode 100644 index 000000000..eceac291e --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/simplebrowser.pyproject @@ -0,0 +1,7 @@ +{ + "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..bf2fef36a --- /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.5.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.Preferred, QSizePolicy.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.MinimumExpanding, QSizePolicy.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.Minimum, QSizePolicy.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..0f98831d4 --- /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.5.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.Expanding, QSizePolicy.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..3522f0758 --- /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.5.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.Fixed, QSizePolicy.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..6a40f30e6 --- /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.5.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.Preferred, QSizePolicy.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..a7f042dcd --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/webview.py @@ -0,0 +1,291 @@ +# 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(":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}\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() |