aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2018-03-29 11:31:21 +0200
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2018-04-30 11:07:05 +0000
commita245785dd9d0f1088aa95ca8890eaeab4bdc1432 (patch)
tree94991b7c0253601767a94025092ad14b0b6ab5f7 /examples
parent6fe563b2aa15c2c23369acce353be69f6ea84c77 (diff)
Start a tabbed browser example
Task-number: PYSIDE-363 Change-Id: Idf7037c1b9efe1ccfce4427a49abc86a6631efa7 Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'examples')
-rw-r--r--examples/webenginewidgets/tabbedbrowser/bookmarkwidget.py267
-rw-r--r--examples/webenginewidgets/tabbedbrowser/browsertabwidget.py220
-rw-r--r--examples/webenginewidgets/tabbedbrowser/downloadwidget.py144
-rw-r--r--examples/webenginewidgets/tabbedbrowser/findtoolbar.py98
-rw-r--r--examples/webenginewidgets/tabbedbrowser/main.py382
-rw-r--r--examples/webenginewidgets/tabbedbrowser/tabbedbrowser.pyqtc6
-rw-r--r--examples/webenginewidgets/tabbedbrowser/webengineview.py90
7 files changed, 1207 insertions, 0 deletions
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())