Web Browser Example¶
The example demonstrates the power and simplicity offered by Qt for Python to developers. It uses several PySide6 submodules to offer a fluid and modern-looking UI that is apt for a web browser. The application offers the following features:
Tab-based browsing experience using QTabWidget.
Download manager using a QProgressBar and QWebEngineDownloadItem.
Bookmark manager using QTreeView.
The application’s code is organized in several parts for ease of maintenance. For example,
DownloadWidget
provides a widget to track progress of a download item. In the following
sections, these different parts are discussed briefly to help you understand the Python code behind
them a little better.
BookmarkWidget or bookmarkwidget.py
¶
This widget docks to the left of the main window by default. It inherits QTreeView and loads a default set of bookmarks using a QStandardItemModel. The model is populated at startup from a JSON file, which is updated when you add or remove bookmarks from the tree view.
DownloadWidget or downloadwidget.py
¶
The widget tracks progress of the download item. It inherits QProgressBar to display progress of the QWebEngineDownloadItem instance, and offers a context-menu with actions such as Launch, Show in folder, Cancel, and Remove.
BrowserTabWidget or browsertabwidget.py
¶
The widget includes a QWebEngineView to enable viewing web content. It docks to the right of BookmarkWidget in the main window.
MainWindow or main.py
¶
This is the parent window that collates all the other widgets together to offer the complete package.
Try running the example to explore it further.
"""PySide6 WebEngineWidgets Example"""
import sys
from bookmarkwidget import BookmarkWidget
from browsertabwidget import BrowserTabWidget
from downloadwidget import DownloadWidget
from findtoolbar import FindToolBar
from webengineview import WebEngineView
from PySide6 import QtCore
from PySide6.QtCore import Qt, QUrl
from PySide6.QtGui import QAction, QKeySequence, QIcon
from PySide6.QtWidgets import (QApplication, QDockWidget, QLabel,
QLineEdit, QMainWindow, QToolBar)
from PySide6.QtWebEngineCore import QWebEngineDownloadRequest, QWebEnginePage
main_windows = []
def create_main_window():
"""Creates a MainWindow using 75% of the available screen resolution."""
main_win = MainWindow()
main_windows.append(main_win)
available_geometry = main_win.screen().availableGeometry()
main_win.resize(available_geometry.width() * 2 / 3,
available_geometry.height() * 2 / 3)
main_win.show()
return main_win
def create_main_window_with_browser():
"""Creates a MainWindow with a BrowserTabWidget."""
main_win = create_main_window()
return main_win.add_browser_tab()
class MainWindow(QMainWindow):
"""Provides the parent window that includes the BookmarkWidget,
BrowserTabWidget, and a DownloadWidget, to offer the complete
web browsing experience."""
def __init__(self):
super().__init__()
self.setWindowTitle('PySide6 tabbed browser Example')
self._tab_widget = BrowserTabWidget(create_main_window_with_browser)
self._tab_widget.enabled_changed.connect(self._enabled_changed)
self._tab_widget.download_requested.connect(self._download_requested)
self.setCentralWidget(self._tab_widget)
self.connect(self._tab_widget, QtCore.SIGNAL("url_changed(QUrl)"),
self.url_changed)
self._bookmark_dock = QDockWidget()
self._bookmark_dock.setWindowTitle('Bookmarks')
self._bookmark_widget = BookmarkWidget()
self._bookmark_widget.open_bookmark.connect(self.load_url)
self._bookmark_widget.open_bookmark_in_new_tab.connect(self.load_url_in_new_tab)
self._bookmark_dock.setWidget(self._bookmark_widget)
self.addDockWidget(Qt.LeftDockWidgetArea, self._bookmark_dock)
self._find_tool_bar = None
self._actions = {}
self._create_menu()
self._tool_bar = QToolBar()
self.addToolBar(self._tool_bar)
for action in self._actions.values():
if not action.icon().isNull():
self._tool_bar.addAction(action)
self._addres_line_edit = QLineEdit()
self._addres_line_edit.setClearButtonEnabled(True)
self._addres_line_edit.returnPressed.connect(self.load)
self._tool_bar.addWidget(self._addres_line_edit)
self._zoom_label = QLabel()
self.statusBar().addPermanentWidget(self._zoom_label)
self._update_zoom_label()
self._bookmarksToolBar = QToolBar()
self.addToolBar(Qt.TopToolBarArea, self._bookmarksToolBar)
self.insertToolBarBreak(self._bookmarksToolBar)
self._bookmark_widget.changed.connect(self._update_bookmarks)
self._update_bookmarks()
def _update_bookmarks(self):
self._bookmark_widget.populate_tool_bar(self._bookmarksToolBar)
self._bookmark_widget.populate_other(self._bookmark_menu, 3)
def _create_menu(self):
file_menu = self.menuBar().addMenu("&File")
exit_action = QAction(QIcon.fromTheme("application-exit"), "E&xit",
self, shortcut="Ctrl+Q", triggered=qApp.quit)
file_menu.addAction(exit_action)
navigation_menu = self.menuBar().addMenu("&Navigation")
style_icons = ':/qt-project.org/styles/commonstyle/images/'
back_action = QAction(QIcon.fromTheme("go-previous",
QIcon(style_icons + 'left-32.png')),
"Back", self,
shortcut=QKeySequence(QKeySequence.Back),
triggered=self._tab_widget.back)
self._actions[QWebEnginePage.Back] = back_action
back_action.setEnabled(False)
navigation_menu.addAction(back_action)
forward_action = QAction(QIcon.fromTheme("go-next",
QIcon(style_icons + 'right-32.png')),
"Forward", self,
shortcut=QKeySequence(QKeySequence.Forward),
triggered=self._tab_widget.forward)
forward_action.setEnabled(False)
self._actions[QWebEnginePage.Forward] = forward_action
navigation_menu.addAction(forward_action)
reload_action = QAction(QIcon(style_icons + 'refresh-32.png'),
"Reload", self,
shortcut=QKeySequence(QKeySequence.Refresh),
triggered=self._tab_widget.reload)
self._actions[QWebEnginePage.Reload] = reload_action
reload_action.setEnabled(False)
navigation_menu.addAction(reload_action)
navigation_menu.addSeparator()
new_tab_action = QAction("New Tab", self,
shortcut='Ctrl+T',
triggered=self.add_browser_tab)
navigation_menu.addAction(new_tab_action)
close_tab_action = QAction("Close Current Tab", self,
shortcut="Ctrl+W",
triggered=self._close_current_tab)
navigation_menu.addAction(close_tab_action)
navigation_menu.addSeparator()
history_action = QAction("History...", self,
triggered=self._tab_widget.show_history)
navigation_menu.addAction(history_action)
edit_menu = self.menuBar().addMenu("&Edit")
find_action = QAction("Find", self,
shortcut=QKeySequence(QKeySequence.Find),
triggered=self._show_find)
edit_menu.addAction(find_action)
edit_menu.addSeparator()
undo_action = QAction("Undo", self,
shortcut=QKeySequence(QKeySequence.Undo),
triggered=self._tab_widget.undo)
self._actions[QWebEnginePage.Undo] = undo_action
undo_action.setEnabled(False)
edit_menu.addAction(undo_action)
redo_action = QAction("Redo", self,
shortcut=QKeySequence(QKeySequence.Redo),
triggered=self._tab_widget.redo)
self._actions[QWebEnginePage.Redo] = redo_action
redo_action.setEnabled(False)
edit_menu.addAction(redo_action)
edit_menu.addSeparator()
cut_action = QAction("Cut", self,
shortcut=QKeySequence(QKeySequence.Cut),
triggered=self._tab_widget.cut)
self._actions[QWebEnginePage.Cut] = cut_action
cut_action.setEnabled(False)
edit_menu.addAction(cut_action)
copy_action = QAction("Copy", self,
shortcut=QKeySequence(QKeySequence.Copy),
triggered=self._tab_widget.copy)
self._actions[QWebEnginePage.Copy] = copy_action
copy_action.setEnabled(False)
edit_menu.addAction(copy_action)
paste_action = QAction("Paste", self,
shortcut=QKeySequence(QKeySequence.Paste),
triggered=self._tab_widget.paste)
self._actions[QWebEnginePage.Paste] = paste_action
paste_action.setEnabled(False)
edit_menu.addAction(paste_action)
edit_menu.addSeparator()
select_all_action = QAction("Select All", self,
shortcut=QKeySequence(QKeySequence.SelectAll),
triggered=self._tab_widget.select_all)
self._actions[QWebEnginePage.SelectAll] = select_all_action
select_all_action.setEnabled(False)
edit_menu.addAction(select_all_action)
self._bookmark_menu = self.menuBar().addMenu("&Bookmarks")
add_bookmark_action = QAction("&Add Bookmark", self,
triggered=self._add_bookmark)
self._bookmark_menu.addAction(add_bookmark_action)
add_tool_bar_bookmark_action = QAction("&Add Bookmark to Tool Bar", self,
triggered=self._add_tool_bar_bookmark)
self._bookmark_menu.addAction(add_tool_bar_bookmark_action)
self._bookmark_menu.addSeparator()
tools_menu = self.menuBar().addMenu("&Tools")
download_action = QAction("Open Downloads", self,
triggered=DownloadWidget.open_download_directory)
tools_menu.addAction(download_action)
window_menu = self.menuBar().addMenu("&Window")
window_menu.addAction(self._bookmark_dock.toggleViewAction())
window_menu.addSeparator()
zoom_in_action = QAction(QIcon.fromTheme("zoom-in"),
"Zoom In", self,
shortcut=QKeySequence(QKeySequence.ZoomIn),
triggered=self._zoom_in)
window_menu.addAction(zoom_in_action)
zoom_out_action = QAction(QIcon.fromTheme("zoom-out"),
"Zoom Out", self,
shortcut=QKeySequence(QKeySequence.ZoomOut),
triggered=self._zoom_out)
window_menu.addAction(zoom_out_action)
reset_zoom_action = QAction(QIcon.fromTheme("zoom-original"),
"Reset Zoom", self,
shortcut="Ctrl+0",
triggered=self._reset_zoom)
window_menu.addAction(reset_zoom_action)
about_menu = self.menuBar().addMenu("&About")
about_action = QAction("About Qt", self,
shortcut=QKeySequence(QKeySequence.HelpContents),
triggered=qApp.aboutQt)
about_menu.addAction(about_action)
def add_browser_tab(self):
return self._tab_widget.add_browser_tab()
def _close_current_tab(self):
if self._tab_widget.count() > 1:
self._tab_widget.close_current_tab()
else:
self.close()
def close_event(self, event):
main_windows.remove(self)
event.accept()
def load(self):
url_string = self._addres_line_edit.text().strip()
if url_string:
self.load_url_string(url_string)
def load_url_string(self, url_s):
url = QUrl.fromUserInput(url_s)
if (url.isValid()):
self.load_url(url)
def load_url(self, url):
self._tab_widget.load(url)
def load_url_in_new_tab(self, url):
self.add_browser_tab().load(url)
def url_changed(self, url):
self._addres_line_edit.setText(url.toString())
def _enabled_changed(self, web_action, enabled):
action = self._actions[web_action]
if action:
action.setEnabled(enabled)
def _add_bookmark(self):
index = self._tab_widget.currentIndex()
if index >= 0:
url = self._tab_widget.url()
title = self._tab_widget.tabText(index)
icon = self._tab_widget.tabIcon(index)
self._bookmark_widget.add_bookmark(url, title, icon)
def _add_tool_bar_bookmark(self):
index = self._tab_widget.currentIndex()
if index >= 0:
url = self._tab_widget.url()
title = self._tab_widget.tabText(index)
icon = self._tab_widget.tabIcon(index)
self._bookmark_widget.add_tool_bar_bookmark(url, title, icon)
def _zoom_in(self):
new_zoom = self._tab_widget.zoom_factor() * 1.5
if (new_zoom <= WebEngineView.maximum_zoom_factor()):
self._tab_widget.set_zoom_factor(new_zoom)
self._update_zoom_label()
def _zoom_out(self):
new_zoom = self._tab_widget.zoom_factor() / 1.5
if (new_zoom >= WebEngineView.minimum_zoom_factor()):
self._tab_widget.set_zoom_factor(new_zoom)
self._update_zoom_label()
def _reset_zoom(self):
self._tab_widget.set_zoom_factor(1)
self._update_zoom_label()
def _update_zoom_label(self):
percent = int(self._tab_widget.zoom_factor() * 100)
self._zoom_label.setText(f"{percent}%")
def _download_requested(self, item):
# Remove old downloads before opening a new one
for old_download in self.statusBar().children():
if (type(old_download).__name__ == 'DownloadWidget' and
old_download.state() != QWebEngineDownloadItem.DownloadInProgress):
self.statusBar().removeWidget(old_download)
del old_download
item.accept()
download_widget = DownloadWidget(item)
download_widget.remove_requested.connect(self._remove_download_requested,
Qt.QueuedConnection)
self.statusBar().addWidget(download_widget)
def _remove_download_requested(self):
download_widget = self.sender()
self.statusBar().removeWidget(download_widget)
del download_widget
def _show_find(self):
if self._find_tool_bar is None:
self._find_tool_bar = FindToolBar()
self._find_tool_bar.find.connect(self._tab_widget.find)
self.addToolBar(Qt.BottomToolBarArea, self._find_tool_bar)
else:
self._find_tool_bar.show()
self._find_tool_bar.focus_find()
def write_bookmarks(self):
self._bookmark_widget.write_bookmarks()
if __name__ == '__main__':
app = QApplication(sys.argv)
main_win = create_main_window()
initial_urls = sys.argv[1:]
if not initial_urls:
initial_urls.append('http://qt.io')
for url in initial_urls:
main_win.load_url_in_new_tab(QUrl.fromUserInput(url))
exit_code = app.exec()
main_win.write_bookmarks()
sys.exit(exit_code)
import json
import os
import warnings
from PySide6 import QtCore
from PySide6.QtCore import QDir, QFileInfo, QStandardPaths, Qt, QUrl
from PySide6.QtGui import QIcon, QStandardItem, QStandardItemModel
from PySide6.QtWidgets import QMenu, QMessageBox, QTreeView
_url_role = Qt.UserRole + 1
# Default bookmarks as an array of arrays which is the form
# used to read from/write to a .json bookmarks file
_default_bookmarks = [
['Tool Bar'],
['http://qt.io', 'Qt', ':/qt-project.org/qmessagebox/images/qtlogo-64.png'],
['https://download.qt.io/snapshots/ci/pyside/', 'Downloads'],
['https://doc.qt.io/qtforpython/', 'Documentation'],
['https://bugreports.qt.io/projects/PYSIDE/', 'Bug Reports'],
['https://www.python.org/', 'Python', None],
['https://wiki.qt.io/PySide6', 'Qt for Python', None],
['Other Bookmarks']
]
def _config_dir():
location = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation)
return f'{location}/QtForPythonBrowser'
_bookmark_file = 'bookmarks.json'
def _create_folder_item(title):
result = QStandardItem(title)
result.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
return result
def _create_item(url, title, icon):
result = QStandardItem(title)
result.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
result.setData(url, _url_role)
if icon is not None:
result.setIcon(icon)
return result
# Create the model from an array of arrays
def _create_model(parent, serialized_bookmarks):
result = QStandardItemModel(0, 1, parent)
last_folder_item = None
for entry in serialized_bookmarks:
if len(entry) == 1:
last_folder_item = _create_folder_item(entry[0])
result.appendRow(last_folder_item)
else:
url = QUrl.fromUserInput(entry[0])
title = entry[1]
icon = QIcon(entry[2]) if len(entry) > 2 and entry[2] else None
last_folder_item.appendRow(_create_item(url, title, icon))
return result
# Serialize model into an array of arrays, writing out the icons
# into .png files under directory in the process
def _serialize_model(model, directory):
result = []
folder_count = model.rowCount()
for f in range(0, folder_count):
folder_item = model.item(f)
result.append([folder_item.text()])
item_count = folder_item.rowCount()
for i in range(0, item_count):
item = folder_item.child(i)
entry = [item.data(_url_role).toString(), item.text()]
icon = item.icon()
if not icon.isNull():
icon_sizes = icon.availableSizes()
largest_size = icon_sizes[len(icon_sizes) - 1]
w = largest_size.width()
icon_file_name = f'{directory}/icon{f:02}_{i:02}_{w}.png'
icon.pixmap(largest_size).save(icon_file_name, 'PNG')
entry.append(icon_file_name)
result.append(entry)
return result
# Bookmarks as a tree view to be used in a dock widget with
# functionality to persist and populate tool bars and menus.
class BookmarkWidget(QTreeView):
"""Provides a tree view to manage the bookmarks."""
open_bookmark = QtCore.Signal(QUrl)
open_bookmark_in_new_tab = QtCore.Signal(QUrl)
changed = QtCore.Signal()
def __init__(self):
super().__init__()
self.setRootIsDecorated(False)
self.setUniformRowHeights(True)
self.setHeaderHidden(True)
self._model = _create_model(self, self._read_bookmarks())
self.setModel(self._model)
self.expandAll()
self.activated.connect(self._activated)
self._model.rowsInserted.connect(self._changed)
self._model.rowsRemoved.connect(self._changed)
self._model.dataChanged.connect(self._changed)
self._modified = False
def _changed(self):
self._modified = True
self.changed.emit()
def _activated(self, index):
item = self._model.itemFromIndex(index)
self.open_bookmark.emit(item.data(_url_role))
def _action_activated(self, index):
action = self.sender()
self.open_bookmark.emit(action.data())
def _tool_bar_item(self):
return self._model.item(0, 0)
def _other_item(self):
return self._model.item(1, 0)
def add_bookmark(self, url, title, icon):
self._other_item().appendRow(_create_item(url, title, icon))
def add_tool_bar_bookmark(self, url, title, icon):
self._tool_bar_item().appendRow(_create_item(url, title, icon))
# Synchronize the bookmarks under parent_item to a target_object
# like QMenu/QToolBar, which has a list of actions. Update
# the existing actions, append new ones if needed or hide
# superfluous ones
def _populate_actions(self, parent_item, target_object, first_action):
existing_actions = target_object.actions()
existing_action_count = len(existing_actions)
a = first_action
row_count = parent_item.rowCount()
for r in range(0, row_count):
item = parent_item.child(r)
title = item.text()
icon = item.icon()
url = item.data(_url_role)
if a < existing_action_count:
action = existing_actions[a]
if (title != action.toolTip()):
action.setText(BookmarkWidget.short_title(title))
action.setIcon(icon)
action.setToolTip(title)
action.setData(url)
action.setVisible(True)
else:
short_title = BookmarkWidget.short_title(title)
action = target_object.addAction(icon, short_title)
action.setToolTip(title)
action.setData(url)
action.triggered.connect(self._action_activated)
a = a + 1
while a < existing_action_count:
existing_actions[a].setVisible(False)
a = a + 1
def populate_tool_bar(self, tool_bar):
self._populate_actions(self._tool_bar_item(), tool_bar, 0)
def populate_other(self, menu, first_action):
self._populate_actions(self._other_item(), menu, first_action)
def _current_item(self):
index = self.currentIndex()
if index.isValid():
item = self._model.itemFromIndex(index)
if item.parent(): # exclude top level items
return item
return None
def context_menu_event(self, event):
context_menu = QMenu()
open_in_new_tab_action = context_menu.addAction("Open in New Tab")
remove_action = context_menu.addAction("Remove...")
current_item = self._current_item()
open_in_new_tab_action.setEnabled(current_item is not None)
remove_action.setEnabled(current_item is not None)
chosen_action = context_menu.exec(event.globalPos())
if chosen_action == open_in_new_tab_action:
self.open_bookmarkInNewTab.emit(current_item.data(_url_role))
elif chosen_action == remove_action:
self._remove_item(current_item)
def _remove_item(self, item):
message = f"Would you like to remove \"{item.text()}\"?"
button = QMessageBox.question(self, "Remove", message,
QMessageBox.Yes | QMessageBox.No)
if button == QMessageBox.Yes:
item.parent().removeRow(item.row())
def write_bookmarks(self):
if not self._modified:
return
dir_path = _config_dir()
native_dir_path = QDir.toNativeSeparators(dir_path)
directory = QFileInfo(dir_path)
if not directory.isDir():
print(f'Creating {native_dir_path}...')
if not QDir(directory.absolutePath()).mkpath(directory.fileName()):
warnings.warn(f'Cannot create {native_dir_path}.',
RuntimeWarning)
return
serialized_model = _serialize_model(self._model, dir_path)
bookmark_file_name = os.path.join(native_dir_path, _bookmark_file)
print(f'Writing {bookmark_file_name}...')
with open(bookmark_file_name, 'w') as bookmark_file:
json.dump(serialized_model, bookmark_file, indent=4)
def _read_bookmarks(self):
bookmark_file_name = os.path.join(QDir.toNativeSeparators(_config_dir()),
_bookmark_file)
if os.path.exists(bookmark_file_name):
print(f'Reading {bookmark_file_name}...')
return json.load(open(bookmark_file_name))
return _default_bookmarks
# Return a short title for a bookmark action,
# "Qt | Cross Platform.." -> "Qt"
@staticmethod
def short_title(t):
i = t.find(' | ')
if i == -1:
i = t.find(' - ')
return t[0:i] if i != -1 else t
from functools import partial
from bookmarkwidget import BookmarkWidget
from webengineview import WebEngineView
from historywindow import HistoryWindow
from PySide6 import QtCore
from PySide6.QtCore import Qt, QUrl
from PySide6.QtWidgets import QMenu, QTabBar, QTabWidget
from PySide6.QtWebEngineCore import QWebEngineDownloadRequest, QWebEnginePage
class BrowserTabWidget(QTabWidget):
"""Enables having several tabs with QWebEngineView."""
url_changed = QtCore.Signal(QUrl)
enabled_changed = QtCore.Signal(QWebEnginePage.WebAction, bool)
download_requested = QtCore.Signal(QWebEngineDownloadRequest)
def __init__(self, window_factory_function):
super().__init__()
self.setTabsClosable(True)
self._window_factory_function = window_factory_function
self._webengineviews = []
self._history_windows = {} # map WebengineView to HistoryWindow
self.currentChanged.connect(self._current_changed)
self.tabCloseRequested.connect(self.handle_tab_close_request)
self._actions_enabled = {}
for web_action in WebEngineView.web_actions():
self._actions_enabled[web_action] = False
tab_bar = self.tabBar()
tab_bar.setSelectionBehaviorOnRemove(QTabBar.SelectPreviousTab)
tab_bar.setContextMenuPolicy(Qt.CustomContextMenu)
tab_bar.customContextMenuRequested.connect(self._handle_tab_context_menu)
def add_browser_tab(self):
factory_func = partial(BrowserTabWidget.add_browser_tab, self)
web_engine_view = WebEngineView(factory_func,
self._window_factory_function)
index = self.count()
self._webengineviews.append(web_engine_view)
title = f'Tab {index + 1}'
self.addTab(web_engine_view, title)
page = web_engine_view.page()
page.titleChanged.connect(self._title_changed)
page.iconChanged.connect(self._icon_changed)
page.profile().downloadRequested.connect(self._download_requested)
web_engine_view.urlChanged.connect(self._url_changed)
web_engine_view.enabled_changed.connect(self._enabled_changed)
self.setCurrentIndex(index)
return web_engine_view
def load(self, url):
index = self.currentIndex()
if index >= 0 and url.isValid():
self._webengineviews[index].setUrl(url)
def find(self, needle, flags):
index = self.currentIndex()
if index >= 0:
self._webengineviews[index].page().findText(needle, flags)
def url(self):
index = self.currentIndex()
return self._webengineviews[index].url() if index >= 0 else QUrl()
def _url_changed(self, url):
index = self.currentIndex()
if index >= 0 and self._webengineviews[index] == self.sender():
self.url_changed.emit(url)
def _title_changed(self, title):
index = self._index_of_page(self.sender())
if (index >= 0):
self.setTabText(index, BookmarkWidget.short_title(title))
def _icon_changed(self, icon):
index = self._index_of_page(self.sender())
if (index >= 0):
self.setTabIcon(index, icon)
def _enabled_changed(self, web_action, enabled):
index = self.currentIndex()
if index >= 0 and self._webengineviews[index] == self.sender():
self._check_emit_enabled_changed(web_action, enabled)
def _check_emit_enabled_changed(self, web_action, enabled):
if enabled != self._actions_enabled[web_action]:
self._actions_enabled[web_action] = enabled
self.enabled_changed.emit(web_action, enabled)
def _current_changed(self, index):
self._update_actions(index)
self.url_changed.emit(self.url())
def _update_actions(self, index):
if index >= 0 and index < len(self._webengineviews):
view = self._webengineviews[index]
for web_action in WebEngineView.web_actions():
enabled = view.is_web_action_enabled(web_action)
self._check_emit_enabled_changed(web_action, enabled)
def back(self):
self._trigger_action(QWebEnginePage.Back)
def forward(self):
self._trigger_action(QWebEnginePage.Forward)
def reload(self):
self._trigger_action(QWebEnginePage.Reload)
def undo(self):
self._trigger_action(QWebEnginePage.Undo)
def redo(self):
self._trigger_action(QWebEnginePage.Redo)
def cut(self):
self._trigger_action(QWebEnginePage.Cut)
def copy(self):
self._trigger_action(QWebEnginePage.Copy)
def paste(self):
self._trigger_action(QWebEnginePage.Paste)
def select_all(self):
self._trigger_action(QWebEnginePage.SelectAll)
def show_history(self):
index = self.currentIndex()
if index >= 0:
webengineview = self._webengineviews[index]
history_window = self._history_windows.get(webengineview)
if not history_window:
history = webengineview.page().history()
history_window = HistoryWindow(history, self)
history_window.open_url.connect(self.load)
history_window.setWindowFlags(history_window.windowFlags()
| Qt.Window)
history_window.setWindowTitle('History')
self._history_windows[webengineview] = history_window
else:
history_window.refresh()
history_window.show()
history_window.raise_()
def zoom_factor(self):
return self._webengineviews[0].zoomFactor() if self._webengineviews else 1.0
def set_zoom_factor(self, z):
for w in self._webengineviews:
w.setZoomFactor(z)
def _handle_tab_context_menu(self, point):
index = self.tabBar().tabAt(point)
if index < 0:
return
tab_count = len(self._webengineviews)
context_menu = QMenu()
duplicate_tab_action = context_menu.addAction("Duplicate Tab")
close_other_tabs_action = context_menu.addAction("Close Other Tabs")
close_other_tabs_action.setEnabled(tab_count > 1)
close_tabs_to_the_right_action = context_menu.addAction("Close Tabs to the Right")
close_tabs_to_the_right_action.setEnabled(index < tab_count - 1)
close_tab_action = context_menu.addAction("&Close Tab")
chosen_action = context_menu.exec(self.tabBar().mapToGlobal(point))
if chosen_action == duplicate_tab_action:
current_url = self.url()
self.add_browser_tab().load(current_url)
elif chosen_action == close_other_tabs_action:
for t in range(tab_count - 1, -1, -1):
if t != index:
self.handle_tab_close_request(t)
elif chosen_action == close_tabs_to_the_right_action:
for t in range(tab_count - 1, index, -1):
self.handle_tab_close_request(t)
elif chosen_action == close_tab_action:
self.handle_tab_close_request(index)
def handle_tab_close_request(self, index):
if (index >= 0 and self.count() > 1):
webengineview = self._webengineviews[index]
if self._history_windows.get(webengineview):
del self._history_windows[webengineview]
self._webengineviews.remove(webengineview)
self.removeTab(index)
def close_current_tab(self):
self.handle_tab_close_request(self.currentIndex())
def _trigger_action(self, action):
index = self.currentIndex()
if index >= 0:
self._webengineviews[index].page().triggerAction(action)
def _index_of_page(self, web_page):
for p in range(0, len(self._webengineviews)):
if (self._webengineviews[p].page() == web_page):
return p
return -1
def _download_requested(self, item):
self.download_requested.emit(item)
import sys
from PySide6 import QtCore
from PySide6.QtCore import QDir, QFileInfo, QStandardPaths, Qt, QUrl
from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import QMenu, QProgressBar, QStyleFactory
from PySide6.QtWebEngineCore import QWebEngineDownloadRequest
# A QProgressBar with context menu for displaying downloads in a QStatusBar.
class DownloadWidget(QProgressBar):
"""Lets you track progress of a QWebEngineDownloadRequest."""
finished = QtCore.Signal()
remove_requested = QtCore.Signal()
def __init__(self, download_item):
super().__init__()
self._download_item = download_item
download_item.finished.connect(self._finished)
download_item.downloadProgress.connect(self._download_progress)
download_item.stateChanged.connect(self._update_tool_tip())
path = download_item.path()
self.setMaximumWidth(300)
# Shorten 'PySide6-5.11.0a1-5.11.0-cp36-cp36m-linux_x86_64.whl'...
description = QFileInfo(path).fileName()
description_length = len(description)
if description_length > 30:
description_ini = description[0:10]
description_end = description[description_length - 10:]
description = f'{description_ini}...{description_end}'
self.setFormat(f'{description} %p%')
self.setOrientation(Qt.Horizontal)
self.setMinimum(0)
self.setValue(0)
self.setMaximum(100)
self._update_tool_tip()
# Force progress bar text to be shown on macoS by using 'fusion' style
if sys.platform == 'darwin':
self.setStyle(QStyleFactory.create('fusion'))
@staticmethod
def open_file(file):
QDesktopServices.openUrl(QUrl.fromLocalFile(file))
@staticmethod
def open_download_directory():
path = QStandardPaths.writableLocation(QStandardPaths.DownloadLocation)
DownloadWidget.open_file(path)
def state(self):
return self._download_item.state()
def _update_tool_tip(self):
path = self._download_item.path()
url_str = self._download_item.url().toString()
native_sep = QDir.toNativeSeparators(path)
tool_tip = f"{url_str}\n{native_sep}"
total_bytes = self._download_item.totalBytes()
if total_bytes > 0:
tool_tip += f"\n{total_bytes / 1024}K"
state = self.state()
if state == QWebEngineDownloadRequest.DownloadRequested:
tool_tip += "\n(requested)"
elif state == QWebEngineDownloadRequest.DownloadInProgress:
tool_tip += "\n(downloading)"
elif state == QWebEngineDownloadRequest.DownloadCompleted:
tool_tip += "\n(completed)"
elif state == QWebEngineDownloadRequest.DownloadCancelled:
tool_tip += "\n(cancelled)"
else:
tool_tip += "\n(interrupted)"
self.setToolTip(tool_tip)
def _download_progress(self, bytes_received, bytes_total):
self.setValue(int(100 * bytes_received / bytes_total))
def _finished(self):
self._update_tool_tip()
self.finished.emit()
def _launch(self):
DownloadWidget.open_file(self._download_item.path())
def mouseDoubleClickEvent(self, event):
if self.state() == QWebEngineDownloadRequest.DownloadCompleted:
self._launch()
def contextMenuEvent(self, event):
state = self.state()
context_menu = QMenu()
launch_action = context_menu.addAction("Launch")
launch_action.setEnabled(state == QWebEngineDownloadRequest.DownloadCompleted)
show_in_folder_action = context_menu.addAction("Show in Folder")
show_in_folder_action.setEnabled(state == QWebEngineDownloadRequest.DownloadCompleted)
cancel_action = context_menu.addAction("Cancel")
cancel_action.setEnabled(state == QWebEngineDownloadRequest.DownloadInProgress)
remove_action = context_menu.addAction("Remove")
remove_action.setEnabled(state != QWebEngineDownloadRequest.DownloadInProgress)
chosen_action = context_menu.exec(event.globalPos())
if chosen_action == launch_action:
self._launch()
elif chosen_action == show_in_folder_action:
path = QFileInfo(self._download_item.path()).absolutePath()
DownloadWidget.open_file(path)
elif chosen_action == cancel_action:
self._download_item.cancel()
elif chosen_action == remove_action:
self.remove_requested.emit()
from PySide6 import QtCore
from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon, QKeySequence
from PySide6.QtWidgets import QCheckBox, QLineEdit, QToolBar, QToolButton
from PySide6.QtWebEngineCore import QWebEnginePage
# A Find tool bar (bottom area)
class FindToolBar(QToolBar):
find = QtCore.Signal(str, QWebEnginePage.FindFlags)
def __init__(self):
super().__init__()
self._line_edit = QLineEdit()
self._line_edit.setClearButtonEnabled(True)
self._line_edit.setPlaceholderText("Find...")
self._line_edit.setMaximumWidth(300)
self._line_edit.returnPressed.connect(self._find_next)
self.addWidget(self._line_edit)
self._previous_button = QToolButton()
style_icons = ':/qt-project.org/styles/commonstyle/images/'
self._previous_button.setIcon(QIcon(style_icons + 'up-32.png'))
self._previous_button.clicked.connect(self._find_previous)
self.addWidget(self._previous_button)
self._next_button = QToolButton()
self._next_button.setIcon(QIcon(style_icons + 'down-32.png'))
self._next_button.clicked.connect(self._find_next)
self.addWidget(self._next_button)
self._case_sensitive_checkbox = QCheckBox('Case Sensitive')
self.addWidget(self._case_sensitive_checkbox)
self._hideButton = QToolButton()
self._hideButton.setShortcut(QKeySequence(Qt.Key_Escape))
self._hideButton.setIcon(QIcon(style_icons + 'closedock-16.png'))
self._hideButton.clicked.connect(self.hide)
self.addWidget(self._hideButton)
def focus_find(self):
self._line_edit.setFocus()
def _emit_find(self, backward):
needle = self._line_edit.text().strip()
if needle:
flags = QWebEnginePage.FindFlags()
if self._case_sensitive_checkbox.isChecked():
flags |= QWebEnginePage.FindCaseSensitively
if backward:
flags |= QWebEnginePage.FindBackward
self.find.emit(needle, flags)
def _find_next(self):
self._emit_find(False)
def _find_previous(self):
self._emit_find(True)
from PySide6.QtWidgets import QApplication, QTreeView
from PySide6.QtCore import Signal, QAbstractTableModel, QModelIndex, Qt, QUrl
class HistoryModel(QAbstractTableModel):
def __init__(self, history, parent=None):
super().__init__(parent)
self._history = history
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return 'Title' if section == 0 else 'Url'
return None
def rowCount(self, index=QModelIndex()):
return self._history.count()
def columnCount(self, index=QModelIndex()):
return 2
def item_at(self, model_index):
return self._history.itemAt(model_index.row())
def data(self, index, role=Qt.DisplayRole):
item = self.item_at(index)
column = index.column()
if role == Qt.DisplayRole:
return item.title() if column == 0 else item.url().toString()
return None
def refresh(self):
self.beginResetModel()
self.endResetModel()
class HistoryWindow(QTreeView):
open_url = Signal(QUrl)
def __init__(self, history, parent):
super().__init__(parent)
self._model = HistoryModel(history, self)
self.setModel(self._model)
self.activated.connect(self._activated)
screen = QApplication.desktop().screenGeometry(parent)
self.resize(screen.width() / 3, screen.height() / 3)
self._adjustSize()
def refresh(self):
self._model.refresh()
self._adjustSize()
def _adjustSize(self):
if (self._model.rowCount() > 0):
self.resizeColumnToContents(0)
def _activated(self, index):
item = self._model.item_at(index)
self.open_url.emit(item.url())
from PySide6.QtWebEngineCore import QWebEnginePage
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6 import QtCore
_web_actions = [QWebEnginePage.Back, QWebEnginePage.Forward,
QWebEnginePage.Reload,
QWebEnginePage.Undo, QWebEnginePage.Redo,
QWebEnginePage.Cut, QWebEnginePage.Copy,
QWebEnginePage.Paste, QWebEnginePage.SelectAll]
class WebEngineView(QWebEngineView):
enabled_changed = QtCore.Signal(QWebEnginePage.WebAction, bool)
@staticmethod
def web_actions():
return _web_actions
@staticmethod
def minimum_zoom_factor():
return 0.25
@staticmethod
def maximum_zoom_factor():
return 5
def __init__(self, tab_factory_func, window_factory_func):
super().__init__()
self._tab_factory_func = tab_factory_func
self._window_factory_func = window_factory_func
page = self.page()
self._actions = {}
for web_action in WebEngineView.web_actions():
action = page.action(web_action)
action.changed.connect(self._enabled_changed)
self._actions[action] = web_action
def is_web_action_enabled(self, web_action):
return self.page().action(web_action).isEnabled()
def createWindow(self, window_type):
if (window_type == QWebEnginePage.WebBrowserTab or
window_type == QWebEnginePage.WebBrowserBackgroundTab):
return self._tab_factory_func()
return self._window_factory_func()
def _enabled_changed(self):
action = self.sender()
web_action = self._actions[action]
self.enabled_changed.emit(web_action, action.isEnabled())
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.