From a245785dd9d0f1088aa95ca8890eaeab4bdc1432 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Thu, 29 Mar 2018 11:31:21 +0200 Subject: Start a tabbed browser example Task-number: PYSIDE-363 Change-Id: Idf7037c1b9efe1ccfce4427a49abc86a6631efa7 Reviewed-by: Alexandru Croitor --- .../tabbedbrowser/bookmarkwidget.py | 267 ++++++++++++++ .../tabbedbrowser/browsertabwidget.py | 220 ++++++++++++ .../tabbedbrowser/downloadwidget.py | 144 ++++++++ .../webenginewidgets/tabbedbrowser/findtoolbar.py | 98 ++++++ examples/webenginewidgets/tabbedbrowser/main.py | 382 +++++++++++++++++++++ .../tabbedbrowser/tabbedbrowser.pyqtc | 6 + .../tabbedbrowser/webengineview.py | 90 +++++ 7 files changed, 1207 insertions(+) create mode 100644 examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py create mode 100644 examples/webenginewidgets/tabbedbrowser/browsertabwidget.py create mode 100644 examples/webenginewidgets/tabbedbrowser/downloadwidget.py create mode 100644 examples/webenginewidgets/tabbedbrowser/findtoolbar.py create mode 100644 examples/webenginewidgets/tabbedbrowser/main.py create mode 100644 examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyqtc create mode 100644 examples/webenginewidgets/tabbedbrowser/webengineview.py (limited to 'examples') diff --git a/examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py b/examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py new file mode 100644 index 000000000..ebcb724d3 --- /dev/null +++ b/examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py @@ -0,0 +1,267 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the PySide examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import json, os, warnings + +from PySide2 import QtCore +from PySide2.QtCore import (QDir, QFileInfo, QModelIndex, QStandardPaths, Qt, + QUrl) +from PySide2.QtGui import QIcon, QPixmap, QStandardItem, QStandardItemModel +from PySide2.QtWidgets import (QAction, QDockWidget, QMenu, QMessageBox, + QToolBar, QTreeView, QWidget) + +_urlRole = Qt.UserRole + 1 + +# Default bookmarks as an array of arrays which is the form +# used to read from/write to a .json bookmarks file +_defaultBookMarks = [ + ['Tool Bar'], + ['http://qt.io', 'Qt', ':/qt-project.org/qmessagebox/images/qtlogo-64.png'], + ['https://download.qt.io/snapshots/ci/pyside/', 'Downloads'], + ['https://doc-snapshots.qt.io/qtforpython/', 'Documentation'], + ['https://bugreports.qt.io/projects/PYSIDE/', 'Bug Reports'], + ['https://www.python.org/', 'Python', None], + ['https://wiki.qt.io/PySide2', 'Qt for Python', None], + ['Other Bookmarks'] +] + +def _configDir(): + return '{}/QtForPythonBrowser'.format( + QStandardPaths.writableLocation(QStandardPaths.ConfigLocation)) + +_bookmarkFile = 'bookmarks.json' + +def _createFolderItem(title): + result = QStandardItem(title) + result.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + return result + +def _createItem(url, title, icon): + result = QStandardItem(title) + result.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + result.setData(url, _urlRole) + if icon is not None: + result.setIcon(icon) + return result + +# Create the model from an array of arrays +def _createModel(parent, serializedBookmarks): + result = QStandardItemModel(0, 1, parent) + lastFolderItem = None + for entry in serializedBookmarks: + if len(entry) == 1: + lastFolderItem = _createFolderItem(entry[0]) + result.appendRow(lastFolderItem) + else: + url = QUrl.fromUserInput(entry[0]) + title = entry[1] + icon = QIcon(entry[2]) if len(entry) > 2 and entry[2] else None + lastFolderItem.appendRow(_createItem(url, title, icon)) + return result + +# Serialize model into an array of arrays, writing out the icons +# into .png files under directory in the process +def _serializeModel(model, directory): + result = [] + folderCount = model.rowCount() + for f in range(0, folderCount): + folderItem = model.item(f) + result.append([folderItem.text()]) + itemCount = folderItem.rowCount() + for i in range(0, itemCount): + item = folderItem.child(i) + entry = [item.data(_urlRole).toString(), item.text()] + icon = item.icon() + if not icon.isNull(): + iconSizes = icon.availableSizes() + largestSize = iconSizes[len(iconSizes) - 1] + iconFileName = '{}/icon{:02}_{:02}_{}.png'.format(directory, + f, i, largestSize.width()) + icon.pixmap(largestSize).save(iconFileName, 'PNG') + entry.append(iconFileName) + result.append(entry) + return result + +# Bookmarks as a tree view to be used in a dock widget with +# functionality to persist and populate tool bars and menus. +class BookmarkWidget(QTreeView): + + openBookmark = QtCore.Signal(QUrl) + openBookmarkInNewTab = QtCore.Signal(QUrl) + changed = QtCore.Signal() + + def __init__(self): + super(BookmarkWidget, self).__init__() + self.setRootIsDecorated(False) + self.setUniformRowHeights(True) + self.setHeaderHidden(True) + self._model = _createModel(self, self._readBookmarks()) + self.setModel(self._model) + self.expandAll() + self.activated.connect(self._activated) + self._model.rowsInserted.connect(self._changed) + self._model.rowsRemoved.connect(self._changed) + self._model.dataChanged.connect(self._changed) + self._modified = False + + def _changed(self): + self._modified = True + self.changed.emit() + + def _activated(self, index): + item = self._model.itemFromIndex(index) + self.openBookmark.emit(item.data(_urlRole)) + + def _actionActivated(self, index): + action = self.sender() + self.openBookmark.emit(action.data()) + + def _toolBarItem(self): + return self._model.item(0, 0) + + def _otherItem(self): + return self._model.item(1, 0) + + def addBookmark(self, url, title, icon): + self._otherItem().appendRow(_createItem(url, title, icon)) + + def addToolBarBookmark(self, url, title, icon): + self._toolBarItem().appendRow(_createItem(url, title, icon)) + + # Synchronize the bookmarks under parentItem to a targetObject + # like QMenu/QToolBar, which has a list of actions. Update + # the existing actions, append new ones if needed or hide + # superfluous ones + def _populateActions(self, parentItem, targetObject, firstAction): + existingActions = targetObject.actions() + existingActionCount = len(existingActions) + a = firstAction + rowCount = parentItem.rowCount() + for r in range(0, rowCount): + item = parentItem.child(r) + title = item.text() + icon = item.icon() + url = item.data(_urlRole) + if a < existingActionCount: + action = existingActions[a] + if (title != action.toolTip()): + action.setText(BookmarkWidget.shortTitle(title)) + action.setIcon(icon) + action.setToolTip(title) + action.setData(url) + action.setVisible(True) + else: + action = targetObject.addAction(icon, BookmarkWidget.shortTitle(title)) + action.setToolTip(title) + action.setData(url) + action.triggered.connect(self._actionActivated) + a = a + 1 + while a < existingActionCount: + existingActions[a].setVisible(False) + a = a + 1 + + def populateToolBar(self, toolBar): + self._populateActions(self._toolBarItem(), toolBar, 0) + + def populateOther(self, menu, firstAction): + self._populateActions(self._otherItem(), menu, firstAction) + + def _currentItem(self): + index = self.currentIndex() + if index.isValid(): + item = self._model.itemFromIndex(index) + if item.parent(): # Exclude top level items + return item + return None + + def contextMenuEvent(self, event): + contextMenu = QMenu() + openInNewTabAction = contextMenu.addAction("Open in New Tab") + removeAction = contextMenu.addAction("Remove...") + currentItem = self._currentItem() + openInNewTabAction.setEnabled(currentItem is not None) + removeAction.setEnabled(currentItem is not None) + chosenAction = contextMenu.exec_(event.globalPos()) + if chosenAction == openInNewTabAction: + self.openBookmarkInNewTab.emit(currentItem.data(_urlRole)) + elif chosenAction == removeAction: + self._removeItem(currentItem) + + def _removeItem(self, item): + button = QMessageBox.question(self, "Remove", + "Would you like to remove \"{}\"?".format(item.text()), + QMessageBox.Yes | QMessageBox.No) + if button == QMessageBox.Yes: + item.parent().removeRow(item.row()) + + def writeBookmarks(self): + if not self._modified: + return + dirPath = _configDir() + nativeDirPath = QDir.toNativeSeparators(dirPath) + dir = QFileInfo(dirPath) + if not dir.isDir(): + print('Creating {}...'.format(nativeDirPath)) + if not QDir(dir.absolutePath()).mkpath(dir.fileName()): + warnings.warn('Cannot create {}.'.format(nativeDirPath), + RuntimeWarning) + return + serializedModel = _serializeModel(self._model, dirPath) + bookmarkFileName = os.path.join(nativeDirPath, _bookmarkFile) + print('Writing {}...'.format(bookmarkFileName)) + with open(bookmarkFileName, 'w') as bookmarkFile: + json.dump(serializedModel, bookmarkFile, indent = 4) + + def _readBookmarks(self): + bookmarkFileName = os.path.join(QDir.toNativeSeparators(_configDir()), + _bookmarkFile) + if os.path.exists(bookmarkFileName): + print('Reading {}...'.format(bookmarkFileName)) + return json.load(open(bookmarkFileName)) + return _defaultBookMarks + + # Return a short title for a bookmark action, + # "Qt | Cross Platform.." -> "Qt" + @staticmethod + def shortTitle(t): + i = t.find(' | ') + if i == -1: + i = t.find(' - ') + return t[0:i] if i != -1 else t diff --git a/examples/webenginewidgets/tabbedbrowser/browsertabwidget.py b/examples/webenginewidgets/tabbedbrowser/browsertabwidget.py new file mode 100644 index 000000000..aa0a30240 --- /dev/null +++ b/examples/webenginewidgets/tabbedbrowser/browsertabwidget.py @@ -0,0 +1,220 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the PySide examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from functools import partial +import sys + +from bookmarkwidget import BookmarkWidget +from webengineview import WebEngineView +from PySide2 import QtCore +from PySide2.QtCore import QPoint, Qt, QUrl +from PySide2.QtWidgets import (QAction, QMenu, QTabBar, QTabWidget) +from PySide2.QtWebEngineWidgets import (QWebEngineDownloadItem, + QWebEnginePage, QWebEngineProfile) + +class BrowserTabWidget(QTabWidget): + + urlChanged = QtCore.Signal(QUrl) + enabledChanged = QtCore.Signal(QWebEnginePage.WebAction, bool) + downloadRequested = QtCore.Signal(QWebEngineDownloadItem) + + def __init__(self, windowFactoryFunction): + super(BrowserTabWidget, self).__init__() + self.setTabsClosable(True) + self._windowFactoryFunction = windowFactoryFunction + self._webengineviews = [] + self.currentChanged.connect(self._currentChanged) + self.tabCloseRequested.connect(self.handleTabCloseRequest) + self._actionsEnabled = {} + for webAction in WebEngineView.webActions(): + self._actionsEnabled[webAction] = False + + tabBar = self.tabBar() + tabBar.setSelectionBehaviorOnRemove(QTabBar.SelectPreviousTab) + tabBar.setContextMenuPolicy(Qt.CustomContextMenu) + tabBar.customContextMenuRequested.connect(self._handleTabContextMenu) + + def addBrowserTab(self): + factoryFunc = partial(BrowserTabWidget.addBrowserTab, self) + webEngineView = WebEngineView(factoryFunc, self._windowFactoryFunction) + index = self.count() + self._webengineviews.append(webEngineView) + title = 'Tab {}'.format(index + 1) + self.addTab(webEngineView, title) + page = webEngineView.page() + page.titleChanged.connect(self._titleChanged) + page.iconChanged.connect(self._iconChanged) + page.profile().downloadRequested.connect(self._downloadRequested) + webEngineView.urlChanged.connect(self._urlChanged) + webEngineView.enabledChanged.connect(self._enabledChanged) + self.setCurrentIndex(index) + return webEngineView + + def load(self, url): + index = self.currentIndex() + if index >= 0 and url.isValid(): + self._webengineviews[index].setUrl(url) + + def find(self, needle, flags): + index = self.currentIndex() + if index >= 0: + self._webengineviews[index].page().findText(needle, flags) + + def url(self): + index = self.currentIndex() + return self._webengineviews[index].url() if index >= 0 else QUrl() + + def _urlChanged(self, url): + index = self.currentIndex() + if index >= 0 and self._webengineviews[index] == self.sender(): + self.urlChanged.emit(url) + + def _titleChanged(self, title): + index = self._indexOfPage(self.sender()) + if (index >= 0): + self.setTabText(index, BookmarkWidget.shortTitle(title)) + + def _iconChanged(self, icon): + index = self._indexOfPage(self.sender()) + if (index >= 0): + self.setTabIcon(index, icon) + + def _enabledChanged(self, webAction, enabled): + index = self.currentIndex() + if index >= 0 and self._webengineviews[index] == self.sender(): + self._checkEmitEnabledChanged(webAction, enabled) + + def _checkEmitEnabledChanged(self, webAction, enabled): + if enabled != self._actionsEnabled[webAction]: + self._actionsEnabled[webAction] = enabled + self.enabledChanged.emit(webAction, enabled) + + def _currentChanged(self, index): + self._updateActions(index) + self.urlChanged.emit(self.url()) + + def _updateActions(self, index): + if index >= 0 and index < len(self._webengineviews): + view = self._webengineviews[index] + for webAction in WebEngineView.webActions(): + enabled = view.isWebActionEnabled(webAction) + self._checkEmitEnabledChanged(webAction, enabled) + + def back(self): + self._triggerAction(QWebEnginePage.Back) + + def forward(self): + self._triggerAction(QWebEnginePage.Forward) + + def reload(self): + self._triggerAction(QWebEnginePage.Reload) + + def undo(self): + self._triggerAction(QWebEnginePage.Undo) + + def redo(self): + self._triggerAction(QWebEnginePage.Redo) + + def cut(self): + self._triggerAction(QWebEnginePage.Cut) + + def copy(self): + self._triggerAction(QWebEnginePage.Copy) + + def paste(self): + self._triggerAction(QWebEnginePage.Paste) + + def selectAll(self): + self._triggerAction(QWebEnginePage.SelectAll) + + def zoomFactor(self): + return self._webengineviews[0].zoomFactor() if self._webengineviews else 1.0 + + def setZoomFactor(self, z): + for w in self._webengineviews: + w.setZoomFactor(z) + + def _handleTabContextMenu(self, point): + index = self.tabBar().tabAt(point) + if index < 0: + return + tabCount = len(self._webengineviews) + contextMenu = QMenu() + duplicateTabAction = contextMenu.addAction("Duplicate Tab") + closeOtherTabsAction = contextMenu.addAction("Close Other Tabs") + closeOtherTabsAction.setEnabled(tabCount > 1) + closeTabsToTheRightAction = contextMenu.addAction("Close Tabs to the Right") + closeTabsToTheRightAction.setEnabled(index < tabCount - 1) + closeTabAction = contextMenu.addAction("&Close Tab") + chosenAction = contextMenu.exec_(self.tabBar().mapToGlobal(point)) + if chosenAction == duplicateTabAction: + currentUrl = self.url() + self.addBrowserTab().load(currentUrl) + elif chosenAction == closeOtherTabsAction: + for t in range(tabCount - 1, -1, -1): + if t != index: + self.handleTabCloseRequest(t) + elif chosenAction == closeTabsToTheRightAction: + for t in range(tabCount - 1, index, -1): + self.handleTabCloseRequest(t) + elif chosenAction == closeTabAction: + self.handleTabCloseRequest(index) + + def handleTabCloseRequest(self, index): + if (index >= 0 and self.count() > 1): + self._webengineviews.remove(self._webengineviews[index]) + self.removeTab(index) + + def closeCurrentTab(self): + self.handleTabCloseRequest(self.currentIndex()) + + def _triggerAction(self, action): + index = self.currentIndex() + if index >= 0: + self._webengineviews[index].page().triggerAction(action) + + def _indexOfPage(self, webPage): + for p in range(0, len(self._webengineviews)): + if (self._webengineviews[p].page() == webPage): + return p + return -1 + + def _downloadRequested(self, item): + self.downloadRequested.emit(item) diff --git a/examples/webenginewidgets/tabbedbrowser/downloadwidget.py b/examples/webenginewidgets/tabbedbrowser/downloadwidget.py new file mode 100644 index 000000000..a49a630de --- /dev/null +++ b/examples/webenginewidgets/tabbedbrowser/downloadwidget.py @@ -0,0 +1,144 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the PySide examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide2 import QtCore +from PySide2.QtCore import QDir, QFileInfo, QStandardPaths, Qt, QUrl +from PySide2.QtGui import QDesktopServices +from PySide2.QtWidgets import (QAction, QLabel, QMenu, QProgressBar, + QStyleFactory, QWidget) +from PySide2.QtWebEngineWidgets import QWebEngineDownloadItem + +# A QProgressBar with context menu for displaying downloads in a QStatusBar. +class DownloadWidget(QProgressBar): + + finished = QtCore.Signal() + removeRequested = QtCore.Signal() + + def __init__(self, downloadItem): + super(DownloadWidget, self).__init__() + self._downloadItem = downloadItem + downloadItem.finished.connect(self._finished) + downloadItem.downloadProgress.connect(self._downloadProgress) + downloadItem.stateChanged.connect(self._updateToolTip()) + path = downloadItem.path() + self.setMaximumWidth(300) + # Shorten 'PySide2-5.11.0a1-5.11.0-cp36-cp36m-linux_x86_64.whl'... + description = QFileInfo(path).fileName() + descriptionLength = len(description) + if descriptionLength > 30: + description = '{}...{}'.format(description[0:10], description[descriptionLength - 10:]) + self.setFormat('{} %p%'.format(description)) + self.setOrientation(Qt.Horizontal) + self.setMinimum(0) + self.setValue(0) + self.setMaximum(100) + self._updateToolTip() + # Force progress bar text to be shown on macoS by using 'fusion' style + if sys.platform == 'darwin': + self.setStyle(QStyleFactory.create('fusion')) + + @staticmethod + def openFile(file): + QDesktopServices.openUrl(QUrl.fromLocalFile(file)) + + @staticmethod + def openDownloadDirectory(): + path = QStandardPaths.writableLocation(QStandardPaths.DownloadLocation) + DownloadWidget.openFile(path) + + def state(self): + return self._downloadItem.state() + + def _updateToolTip(self): + path = self._downloadItem.path() + toolTip = "{}\n{}".format(self._downloadItem.url().toString(), + QDir.toNativeSeparators(path)) + totalBytes = self._downloadItem.totalBytes() + if totalBytes > 0: + toolTip += "\n{}K".format(totalBytes / 1024) + state = self.state() + if state == QWebEngineDownloadItem.DownloadRequested: + toolTip += "\n(requested)" + elif state == QWebEngineDownloadItem.DownloadInProgress: + toolTip += "\n(downloading)" + elif state == QWebEngineDownloadItem.DownloadCompleted: + toolTip += "\n(completed)" + elif state == QWebEngineDownloadItem.DownloadCancelled: + toolTip += "\n(cancelled)" + else: + toolTip += "\n(interrupted)" + self.setToolTip(toolTip) + + def _downloadProgress(self, bytesReceived, bytesTotal): + self.setValue(int(100 * bytesReceived / bytesTotal)) + + def _finished(self): + self._updateToolTip() + self.finished.emit() + + def _launch(self): + DownloadWidget.openFile(self._downloadItem.path()) + + def mouseDoubleClickEvent(self, event): + if self.state() == QWebEngineDownloadItem.DownloadCompleted: + self._launch() + + def contextMenuEvent(self, event): + state = self.state() + contextMenu = QMenu() + launchAction = contextMenu.addAction("Launch") + launchAction.setEnabled(state == QWebEngineDownloadItem.DownloadCompleted) + showInFolderAction = contextMenu.addAction("Show in Folder") + showInFolderAction.setEnabled(state == QWebEngineDownloadItem.DownloadCompleted) + cancelAction = contextMenu.addAction("Cancel") + cancelAction.setEnabled(state == QWebEngineDownloadItem.DownloadInProgress) + removeAction = contextMenu.addAction("Remove") + removeAction.setEnabled(state != QWebEngineDownloadItem.DownloadInProgress) + + chosenAction = contextMenu.exec_(event.globalPos()) + if chosenAction == launchAction: + self._launch() + elif chosenAction == showInFolderAction: + DownloadWidget.openFile(QFileInfo(self._downloadItem.path()).absolutePath()) + elif chosenAction == cancelAction: + self._downloadItem.cancel() + elif chosenAction == removeAction: + self.removeRequested.emit() diff --git a/examples/webenginewidgets/tabbedbrowser/findtoolbar.py b/examples/webenginewidgets/tabbedbrowser/findtoolbar.py new file mode 100644 index 000000000..c0c960850 --- /dev/null +++ b/examples/webenginewidgets/tabbedbrowser/findtoolbar.py @@ -0,0 +1,98 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the PySide examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from PySide2 import QtCore +from PySide2.QtCore import Qt, QUrl +from PySide2.QtGui import QIcon, QKeySequence +from PySide2.QtWidgets import (QAction, QCheckBox, QDockWidget, QHBoxLayout, + QLabel, QLineEdit, QToolBar, QToolButton, QWidget) +from PySide2.QtWebEngineWidgets import QWebEnginePage + +# A Find tool bar (bottom area) +class FindToolBar(QToolBar): + + find = QtCore.Signal(str, QWebEnginePage.FindFlags) + + def __init__(self): + super(FindToolBar, self).__init__() + self._lineEdit = QLineEdit() + self._lineEdit.setClearButtonEnabled(True) + self._lineEdit.setPlaceholderText("Find...") + self._lineEdit.setMaximumWidth(300) + self._lineEdit.returnPressed.connect(self._findNext) + self.addWidget(self._lineEdit) + + self._previousButton = QToolButton() + self._previousButton.setIcon(QIcon(':/qt-project.org/styles/commonstyle/images/up-32.png')) + self._previousButton.clicked.connect(self._findPrevious) + self.addWidget(self._previousButton) + + self._nextButton = QToolButton() + self._nextButton.setIcon(QIcon(':/qt-project.org/styles/commonstyle/images/down-32.png')) + self._nextButton.clicked.connect(self._findNext) + self.addWidget(self._nextButton) + + self._caseSensitiveCheckBox = QCheckBox('Case Sensitive') + self.addWidget(self._caseSensitiveCheckBox) + + self._hideButton = QToolButton() + self._hideButton.setShortcut(QKeySequence(Qt.Key_Escape)) + self._hideButton.setIcon(QIcon(':/qt-project.org/styles/macstyle/images/closedock-16.png')) + self._hideButton.clicked.connect(self.hide) + self.addWidget(self._hideButton) + + def focusFind(self): + self._lineEdit.setFocus() + + def _emitFind(self, backward): + needle = self._lineEdit.text().strip() + if needle: + flags = QWebEnginePage.FindFlags() + if self._caseSensitiveCheckBox.isChecked(): + flags |= QWebEnginePage.FindCaseSensitively + if backward: + flags |= QWebEnginePage.FindBackward + self.find.emit(needle, flags) + + def _findNext(self): + self._emitFind(False) + + def _findPrevious(self): + self._emitFind(True) diff --git a/examples/webenginewidgets/tabbedbrowser/main.py b/examples/webenginewidgets/tabbedbrowser/main.py new file mode 100644 index 000000000..c46d95b07 --- /dev/null +++ b/examples/webenginewidgets/tabbedbrowser/main.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python + +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the PySide examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +"""PySide2 WebEngineWidgets Example""" + +import sys +from bookmarkwidget import BookmarkWidget +from browsertabwidget import BrowserTabWidget +from downloadwidget import DownloadWidget +from findtoolbar import FindToolBar +from webengineview import QWebEnginePage, WebEngineView +from PySide2 import QtCore +from PySide2.QtCore import Qt, QUrl +from PySide2.QtGui import QCloseEvent, QKeySequence, QIcon +from PySide2.QtWidgets import (qApp, QAction, QApplication, QDesktopWidget, + QDockWidget, QLabel, QLineEdit, QMainWindow, QMenu, QMenuBar, QPushButton, + QStatusBar, QToolBar) +from PySide2.QtWebEngineWidgets import (QWebEngineDownloadItem, QWebEnginePage, + QWebEngineView) + +mainWindows = [] + +def createMainWindow(): + mainWin = MainWindow() + mainWindows.append(mainWin) + availableGeometry = app.desktop().availableGeometry(mainWin) + mainWin.resize(availableGeometry.width() * 2 / 3, availableGeometry.height() * 2 / 3) + mainWin.show() + return mainWin + +def createMainWindowWithBrowser(): + mainWin = createMainWindow() + return mainWin.addBrowserTab() + +class MainWindow(QMainWindow): + + def __init__(self): + super(MainWindow, self).__init__() + + self.setWindowTitle('PySide2 Tabbed Browser Example') + + self._tabWidget = BrowserTabWidget(createMainWindowWithBrowser) + self._tabWidget.enabledChanged.connect(self._enabledChanged) + self._tabWidget.downloadRequested.connect(self._downloadRequested) + self.setCentralWidget(self._tabWidget) + self.connect(self._tabWidget, QtCore.SIGNAL("urlChanged(QUrl)"), + self.urlChanged) + + self._bookmarkDock = QDockWidget() + self._bookmarkDock.setWindowTitle('Bookmarks') + self._bookmarkWidget = BookmarkWidget() + self._bookmarkWidget.openBookmark.connect(self.loadUrl) + self._bookmarkWidget.openBookmarkInNewTab.connect(self.loadUrlInNewTab) + self._bookmarkDock.setWidget(self._bookmarkWidget) + self.addDockWidget(Qt.LeftDockWidgetArea, self._bookmarkDock) + + self._findToolBar = None + + self._actions = {} + self._createMenu() + + self._toolBar = QToolBar() + self.addToolBar(self._toolBar) + for action in self._actions.values(): + if not action.icon().isNull(): + self._toolBar.addAction(action) + + self._addressLineEdit = QLineEdit() + self._addressLineEdit.setClearButtonEnabled(True) + self._addressLineEdit.returnPressed.connect(self.load) + self._toolBar.addWidget(self._addressLineEdit) + self._zoomLabel = QLabel() + self.statusBar().addPermanentWidget(self._zoomLabel) + self._updateZoomLabel() + + self._bookmarksToolBar = QToolBar() + self.addToolBar(Qt.TopToolBarArea, self._bookmarksToolBar) + self.insertToolBarBreak(self._bookmarksToolBar) + self._bookmarkWidget.changed.connect(self._updateBookmarks) + self._updateBookmarks() + + def _updateBookmarks(self): + self._bookmarkWidget.populateToolBar(self._bookmarksToolBar) + self._bookmarkWidget.populateOther(self._bookmarkMenu, 3) + + def _createMenu(self): + fileMenu = self.menuBar().addMenu("&File") + exitAction = QAction(QIcon.fromTheme("application-exit"), "E&xit", + self, shortcut = "Ctrl+Q", triggered=qApp.quit) + fileMenu.addAction(exitAction) + + navigationMenu = self.menuBar().addMenu("&Navigation") + + styleIcons = ':/qt-project.org/styles/commonstyle/images/' + backAction = QAction(QIcon.fromTheme("go-previous", + QIcon(styleIcons + 'left-32.png')), + "Back", self, + shortcut = QKeySequence(QKeySequence.Back), + triggered = self._tabWidget.back) + self._actions[QWebEnginePage.Back] = backAction + backAction.setEnabled(False) + navigationMenu.addAction(backAction) + forwardAction = QAction(QIcon.fromTheme("go-next", + QIcon(styleIcons + 'right-32.png')), + "Forward", self, + shortcut = QKeySequence(QKeySequence.Forward), + triggered = self._tabWidget.forward) + forwardAction.setEnabled(False) + self._actions[QWebEnginePage.Forward] = forwardAction + + navigationMenu.addAction(forwardAction) + reloadAction = QAction(QIcon(styleIcons + 'refresh-32.png'), + "Reload", self, + shortcut = QKeySequence(QKeySequence.Refresh), + triggered = self._tabWidget.reload) + self._actions[QWebEnginePage.Reload] = reloadAction + reloadAction.setEnabled(False) + navigationMenu.addAction(reloadAction) + + navigationMenu.addSeparator() + + newTabAction = QAction("New Tab", self, + shortcut = 'Ctrl+T', + triggered = self.addBrowserTab) + navigationMenu.addAction(newTabAction) + + closeTabAction = QAction("Close Current Tab", self, + shortcut = "Ctrl+W", + triggered = self._closeCurrentTab) + navigationMenu.addAction(closeTabAction) + + editMenu = self.menuBar().addMenu("&Edit") + + findAction = QAction("Find", self, + shortcut = QKeySequence(QKeySequence.Find), + triggered = self._showFind) + editMenu.addAction(findAction) + + editMenu.addSeparator() + undoAction = QAction("Undo", self, + shortcut = QKeySequence(QKeySequence.Undo), + triggered = self._tabWidget.undo) + self._actions[QWebEnginePage.Undo] = undoAction + undoAction.setEnabled(False) + editMenu.addAction(undoAction) + + redoAction = QAction("Redo", self, + shortcut = QKeySequence(QKeySequence.Redo), + triggered = self._tabWidget.redo) + self._actions[QWebEnginePage.Redo] = redoAction + redoAction.setEnabled(False) + editMenu.addAction(redoAction) + + editMenu.addSeparator() + + cutAction = QAction("Cut", self, + shortcut = QKeySequence(QKeySequence.Cut), + triggered = self._tabWidget.cut) + self._actions[QWebEnginePage.Cut] = cutAction + cutAction.setEnabled(False) + editMenu.addAction(cutAction) + + copyAction = QAction("Copy", self, + shortcut = QKeySequence(QKeySequence.Copy), + triggered = self._tabWidget.copy) + self._actions[QWebEnginePage.Copy] = copyAction + copyAction.setEnabled(False) + editMenu.addAction(copyAction) + + pasteAction = QAction("Paste", self, + shortcut = QKeySequence(QKeySequence.Paste), + triggered = self._tabWidget.paste) + self._actions[QWebEnginePage.Paste] = pasteAction + pasteAction.setEnabled(False) + editMenu.addAction(pasteAction) + + editMenu.addSeparator() + + selectAllAction = QAction("Select All", self, + shortcut = QKeySequence(QKeySequence.SelectAll), + triggered = self._tabWidget.selectAll) + self._actions[QWebEnginePage.SelectAll] = selectAllAction + selectAllAction.setEnabled(False) + editMenu.addAction(selectAllAction) + + self._bookmarkMenu = self.menuBar().addMenu("&Bookmarks") + addBookmarkAction = QAction("&Add Bookmark", self, + triggered = self._addBookmark) + self._bookmarkMenu.addAction(addBookmarkAction) + addToolBarBookmarkAction = QAction("&Add Bookmark to Tool Bar", self, + triggered = self._addToolBarBookmark) + self._bookmarkMenu.addAction(addToolBarBookmarkAction) + self._bookmarkMenu.addSeparator() + + toolsMenu = self.menuBar().addMenu("&Tools") + downloadAction = QAction("Open Downloads", self, + triggered = DownloadWidget.openDownloadDirectory) + toolsMenu.addAction(downloadAction) + + windowMenu = self.menuBar().addMenu("&Window") + + windowMenu.addAction(self._bookmarkDock.toggleViewAction()) + + windowMenu.addSeparator() + + zoomInAction = QAction(QIcon.fromTheme("zoom-in"), + "Zoom In", self, + shortcut = QKeySequence(QKeySequence.ZoomIn), + triggered = self._zoomIn) + windowMenu.addAction(zoomInAction) + zoomOutAction = QAction(QIcon.fromTheme("zoom-out"), + "Zoom Out", self, + shortcut = QKeySequence(QKeySequence.ZoomOut), + triggered = self._zoomOut) + windowMenu.addAction(zoomOutAction) + + resetZoomAction = QAction(QIcon.fromTheme("zoom-original"), + "Reset Zoom", self, + shortcut = "Ctrl+0", + triggered = self._resetZoom) + windowMenu.addAction(resetZoomAction) + + aboutMenu = self.menuBar().addMenu("&About") + aboutAction = QAction("About Qt", self, + shortcut = QKeySequence(QKeySequence.HelpContents), + triggered=qApp.aboutQt) + aboutMenu.addAction(aboutAction) + + def addBrowserTab(self): + return self._tabWidget.addBrowserTab() + + def _closeCurrentTab(self): + if self._tabWidget.count() > 1: + self._tabWidget.closeCurrentTab() + else: + self.close() + + def closeEvent(self, event): + mainWindows.remove(self) + event.accept() + + def load(self): + urlString = self._addressLineEdit.text().strip() + if urlString: + self.loadUrlString(urlString) + + def loadUrlString(self, urlS): + url = QUrl.fromUserInput(urlS) + if (url.isValid()): + self.loadUrl(url) + + def loadUrl(self, url): + self._tabWidget.load(url) + + def loadUrlInNewTab(self, url): + self.addBrowserTab().load(url) + + def urlChanged(self, url): + self._addressLineEdit.setText(url.toString()) + + def _enabledChanged(self, webAction, enabled): + action = self._actions[webAction] + if action: + action.setEnabled(enabled) + + def _addBookmark(self): + index = self._tabWidget.currentIndex() + if index >= 0: + url = self._tabWidget.url() + title = self._tabWidget.tabText(index) + icon = self._tabWidget.tabIcon(index) + self._bookmarkWidget.addBookmark(url, title, icon) + + def _addToolBarBookmark(self): + index = self._tabWidget.currentIndex() + if index >= 0: + url = self._tabWidget.url() + title = self._tabWidget.tabText(index) + icon = self._tabWidget.tabIcon(index) + self._bookmarkWidget.addToolBarBookmark(url, title, icon) + + def _zoomIn(self): + newZoom = self._tabWidget.zoomFactor() * 1.5 + if (newZoom <= WebEngineView.maximumZoomFactor()): + self._tabWidget.setZoomFactor(newZoom) + self._updateZoomLabel() + + def _zoomOut(self): + newZoom = self._tabWidget.zoomFactor() / 1.5 + if (newZoom >= WebEngineView.minimumZoomFactor()): + self._tabWidget.setZoomFactor(newZoom) + self._updateZoomLabel() + + def _resetZoom(self): + self._tabWidget.setZoomFactor(1) + self._updateZoomLabel() + + def _updateZoomLabel(self): + percent = int(self._tabWidget.zoomFactor() * 100) + self._zoomLabel.setText("{}%".format(percent)) + + def _downloadRequested(self, item): + # Remove old downloads before opening a new one + for oldDownload in self.statusBar().children(): + if type(oldDownload).__name__ == 'DownloadWidget' and \ + oldDownload.state() != QWebEngineDownloadItem.DownloadInProgress: + self.statusBar().removeWidget(oldDownload) + del oldDownload + + item.accept() + downloadWidget = DownloadWidget(item) + downloadWidget.removeRequested.connect(self._removeDownloadRequested, + Qt.QueuedConnection) + self.statusBar().addWidget(downloadWidget) + + def _removeDownloadRequested(self): + downloadWidget = self.sender() + self.statusBar().removeWidget(downloadWidget) + del downloadWidget + + def _showFind(self): + if self._findToolBar is None: + self._findToolBar = FindToolBar() + self._findToolBar.find.connect(self._tabWidget.find) + self.addToolBar(Qt.BottomToolBarArea, self._findToolBar) + else: + self._findToolBar.show() + self._findToolBar.focusFind() + + def writeBookmarks(self): + self._bookmarkWidget.writeBookmarks() + +if __name__ == '__main__': + app = QApplication(sys.argv) + mainWin = createMainWindow() + initialUrls = sys.argv[1:] + if not initialUrls: + initialUrls.append('http://qt.io') + for url in initialUrls: + mainWin.loadUrlInNewTab(QUrl.fromUserInput(url)) + exitCode = app.exec_() + mainWin.writeBookmarks() + sys.exit(exitCode) diff --git a/examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyqtc b/examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyqtc new file mode 100644 index 000000000..1ad61c350 --- /dev/null +++ b/examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyqtc @@ -0,0 +1,6 @@ +main.py +bookmarkwidget.py +browsertabwidget.py +downloadwidget.py +findtoolbar.py +webengineview.py diff --git a/examples/webenginewidgets/tabbedbrowser/webengineview.py b/examples/webenginewidgets/tabbedbrowser/webengineview.py new file mode 100644 index 000000000..b1b21930b --- /dev/null +++ b/examples/webenginewidgets/tabbedbrowser/webengineview.py @@ -0,0 +1,90 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the PySide examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide2.QtWebEngineWidgets import QWebEnginePage, QWebEngineView + +from PySide2 import QtCore + +_webActions = [QWebEnginePage.Back, QWebEnginePage.Forward, + QWebEnginePage.Reload, + QWebEnginePage.Undo, QWebEnginePage.Redo, + QWebEnginePage.Cut, QWebEnginePage.Copy, + QWebEnginePage.Paste, QWebEnginePage.SelectAll] + +class WebEngineView(QWebEngineView): + + enabledChanged = QtCore.Signal(QWebEnginePage.WebAction, bool) + + @staticmethod + def webActions(): + return _webActions + + @staticmethod + def minimumZoomFactor(): + return 0.25 + + @staticmethod + def maximumZoomFactor(): + return 5 + + def __init__(self, tabFactoryFunc, windowFactoryFunc): + super(WebEngineView, self).__init__() + self._tabFactoryFunc = tabFactoryFunc + self._windowFactoryFunc = windowFactoryFunc + page = self.page() + self._actions = {} + for webAction in WebEngineView.webActions(): + action = page.action(webAction) + action.changed.connect(self._enabledChanged) + self._actions[action] = webAction + + def isWebActionEnabled(self, webAction): + return self.page().action(webAction).isEnabled() + + def createWindow(self, windowType): + if windowType == QWebEnginePage.WebBrowserTab or windowType == QWebEnginePage.WebBrowserBackgroundTab: + return self._tabFactoryFunc() + return self._windowFactoryFunc() + + def _enabledChanged(self): + action = self.sender() + webAction = self._actions[action] + self.enabledChanged.emit(webAction, action.isEnabled()) -- cgit v1.2.3