aboutsummaryrefslogtreecommitdiffstats
path: root/examples/webenginewidgets/simplebrowser/browserwindow.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/webenginewidgets/simplebrowser/browserwindow.py')
-rw-r--r--examples/webenginewidgets/simplebrowser/browserwindow.py500
1 files changed, 500 insertions, 0 deletions
diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.py b/examples/webenginewidgets/simplebrowser/browserwindow.py
new file mode 100644
index 000000000..43b811200
--- /dev/null
+++ b/examples/webenginewidgets/simplebrowser/browserwindow.py
@@ -0,0 +1,500 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtWebEngineCore import QWebEnginePage
+from PySide6.QtWidgets import (QMainWindow, QFileDialog,
+ QInputDialog, QLineEdit, QMenu, QMessageBox,
+ QProgressBar, QToolBar, QVBoxLayout, QWidget)
+from PySide6.QtGui import QAction, QGuiApplication, QIcon, QKeySequence
+from PySide6.QtCore import QUrl, Qt, Slot, Signal
+
+from tabwidget import TabWidget
+
+
+def remove_backspace(keys):
+ result = keys.copy()
+ # Chromium already handles navigate on backspace when appropriate.
+ for i, key in enumerate(result):
+ if (key[0].key() & Qt.Key_unknown) == Qt.Key_Backspace:
+ del result[i]
+ break
+ return result
+
+
+class BrowserWindow(QMainWindow):
+
+ about_to_close = Signal()
+
+ def __init__(self, browser, profile, forDevTools):
+ super().__init__()
+
+ self._progress_bar = None
+ self._history_back_action = None
+ self._history_forward_action = None
+ self._stop_action = None
+ self._reload_action = None
+ self._stop_reload_action = None
+ self._url_line_edit = None
+ self._fav_action = None
+ self._last_search = ""
+ self._toolbar = None
+
+ self._browser = browser
+ self._profile = profile
+ self._tab_widget = TabWidget(profile, self)
+
+ self._stop_icon = QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop,
+ QIcon(":process-stop.png"))
+ self._reload_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh,
+ QIcon(":view-refresh.png"))
+
+ self.setAttribute(Qt.WA_DeleteOnClose, True)
+ self.setFocusPolicy(Qt.ClickFocus)
+
+ if not forDevTools:
+ self._progress_bar = QProgressBar(self)
+
+ self._toolbar = self.create_tool_bar()
+ self.addToolBar(self._toolbar)
+ mb = self.menuBar()
+ mb.addMenu(self.create_file_menu(self._tab_widget))
+ mb.addMenu(self.create_edit_menu())
+ mb.addMenu(self.create_view_menu())
+ mb.addMenu(self.create_window_menu(self._tab_widget))
+ mb.addMenu(self.create_help_menu())
+
+ central_widget = QWidget(self)
+ layout = QVBoxLayout(central_widget)
+ layout.setSpacing(0)
+ layout.setContentsMargins(0, 0, 0, 0)
+ if not forDevTools:
+ self.addToolBarBreak()
+
+ self._progress_bar.setMaximumHeight(1)
+ self._progress_bar.setTextVisible(False)
+ s = "QProgressBar {border: 0px} QProgressBar.chunk {background-color: #da4453}"
+ self._progress_bar.setStyleSheet(s)
+
+ layout.addWidget(self._progress_bar)
+
+ layout.addWidget(self._tab_widget)
+ self.setCentralWidget(central_widget)
+
+ self._tab_widget.title_changed.connect(self.handle_web_view_title_changed)
+ if not forDevTools:
+ self._tab_widget.link_hovered.connect(self._show_status_message)
+ self._tab_widget.load_progress.connect(self.handle_web_view_load_progress)
+ self._tab_widget.web_action_enabled_changed.connect(
+ self.handle_web_action_enabled_changed)
+ self._tab_widget.url_changed.connect(self._url_changed)
+ self._tab_widget.fav_icon_changed.connect(self._fav_action.setIcon)
+ self._tab_widget.dev_tools_requested.connect(self.handle_dev_tools_requested)
+ self._url_line_edit.returnPressed.connect(self._address_return_pressed)
+ self._tab_widget.find_text_finished.connect(self.handle_find_text_finished)
+
+ focus_url_line_edit_action = QAction(self)
+ self.addAction(focus_url_line_edit_action)
+ focus_url_line_edit_action.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_L))
+ focus_url_line_edit_action.triggered.connect(self._focus_url_lineEdit)
+
+ self.handle_web_view_title_changed("")
+ self._tab_widget.create_tab()
+
+ @Slot(str)
+ def _show_status_message(self, m):
+ self.statusBar().showMessage(m)
+
+ @Slot(QUrl)
+ def _url_changed(self, url):
+ self._url_line_edit.setText(url.toDisplayString())
+
+ @Slot()
+ def _address_return_pressed(self):
+ url = QUrl.fromUserInput(self._url_line_edit.text())
+ self._tab_widget.set_url(url)
+
+ @Slot()
+ def _focus_url_lineEdit(self):
+ self._url_line_edit.setFocus(Qt.ShortcutFocusReason)
+
+ @Slot()
+ def _new_tab(self):
+ self._tab_widget.create_tab()
+ self._url_line_edit.setFocus()
+
+ @Slot()
+ def _close_current_tab(self):
+ self._tab_widget.close_tab(self._tab_widget.currentIndex())
+
+ @Slot()
+ def _update_close_action_text(self):
+ last_win = len(self._browser.windows()) == 1
+ self._close_action.setText("Quit" if last_win else "Close Window")
+
+ def sizeHint(self):
+ desktop_rect = QGuiApplication.primaryScreen().geometry()
+ return desktop_rect.size() * 0.9
+
+ def create_file_menu(self, tabWidget):
+ file_menu = QMenu("File")
+ file_menu.addAction("&New Window", QKeySequence.New,
+ self.handle_new_window_triggered)
+ file_menu.addAction("New &Incognito Window",
+ self.handle_new_incognito_window_triggered)
+
+ new_tab_action = QAction("New Tab", self)
+ new_tab_action.setShortcuts(QKeySequence.AddTab)
+ new_tab_action.triggered.connect(self._new_tab)
+ file_menu.addAction(new_tab_action)
+
+ file_menu.addAction("&Open File...", QKeySequence.Open,
+ self.handle_file_open_triggered)
+ file_menu.addSeparator()
+
+ close_tab_action = QAction("Close Tab", self)
+ close_tab_action.setShortcuts(QKeySequence.Close)
+ close_tab_action.triggered.connect(self._close_current_tab)
+ file_menu.addAction(close_tab_action)
+
+ self._close_action = QAction("Quit", self)
+ self._close_action.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Q))
+ self._close_action.triggered.connect(self.close)
+ file_menu.addAction(self._close_action)
+
+ file_menu.aboutToShow.connect(self._update_close_action_text)
+ return file_menu
+
+ @Slot()
+ def _find_next(self):
+ tab = self.current_tab()
+ if tab and self._last_search:
+ tab.findText(self._last_search)
+
+ @Slot()
+ def _find_previous(self):
+ tab = self.current_tab()
+ if tab and self._last_search:
+ tab.findText(self._last_search, QWebEnginePage.FindBackward)
+
+ def create_edit_menu(self):
+ edit_menu = QMenu("Edit")
+ find_action = edit_menu.addAction("Find")
+ find_action.setShortcuts(QKeySequence.Find)
+ find_action.triggered.connect(self.handle_find_action_triggered)
+
+ find_next_action = edit_menu.addAction("Find Next")
+ find_next_action.setShortcut(QKeySequence.FindNext)
+ find_next_action.triggered.connect(self._find_next)
+
+ find_previous_action = edit_menu.addAction("Find Previous")
+ find_previous_action.setShortcut(QKeySequence.FindPrevious)
+ find_previous_action.triggered.connect(self._find_previous)
+ return edit_menu
+
+ @Slot()
+ def _stop(self):
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.Stop)
+
+ @Slot()
+ def _reload(self):
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.Reload)
+
+ @Slot()
+ def _zoom_in(self):
+ tab = self.current_tab()
+ if tab:
+ tab.setZoomFactor(tab.zoomFactor() + 0.1)
+
+ @Slot()
+ def _zoom_out(self):
+ tab = self.current_tab()
+ if tab:
+ tab.setZoomFactor(tab.zoomFactor() - 0.1)
+
+ @Slot()
+ def _reset_zoom(self):
+ tab = self.current_tab()
+ if tab:
+ tab.setZoomFactor(1)
+
+ @Slot()
+ def _toggle_toolbar(self):
+ if self._toolbar.isVisible():
+ self._view_toolbar_action.setText("Show Toolbar")
+ self._toolbar.close()
+ else:
+ self._view_toolbar_action.setText("Hide Toolbar")
+ self._toolbar.show()
+
+ @Slot()
+ def _toggle_statusbar(self):
+ sb = self.statusBar()
+ if sb.isVisible():
+ self._view_statusbar_action.setText("Show Status Bar")
+ sb.close()
+ else:
+ self._view_statusbar_action.setText("Hide Status Bar")
+ sb.show()
+
+ def create_view_menu(self):
+ view_menu = QMenu("View")
+ self._stop_action = view_menu.addAction("Stop")
+ shortcuts = []
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Period))
+ shortcuts.append(QKeySequence(Qt.Key_Escape))
+ self._stop_action.setShortcuts(shortcuts)
+ self._stop_action.triggered.connect(self._stop)
+
+ self._reload_action = view_menu.addAction("Reload Page")
+ self._reload_action.setShortcuts(QKeySequence.Refresh)
+ self._reload_action.triggered.connect(self._reload)
+
+ zoom_in = view_menu.addAction("Zoom In")
+ zoom_in.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Plus))
+ zoom_in.triggered.connect(self._zoom_in)
+
+ zoom_out = view_menu.addAction("Zoom Out")
+ zoom_out.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Minus))
+ zoom_out.triggered.connect(self._zoom_out)
+
+ reset_zoom = view_menu.addAction("Reset Zoom")
+ reset_zoom.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_0))
+ reset_zoom.triggered.connect(self._reset_zoom)
+
+ view_menu.addSeparator()
+ self._view_toolbar_action = QAction("Hide Toolbar", self)
+ self._view_toolbar_action.setShortcut("Ctrl+|")
+ self._view_toolbar_action.triggered.connect(self._toggle_toolbar)
+ view_menu.addAction(self._view_toolbar_action)
+
+ self._view_statusbar_action = QAction("Hide Status Bar", self)
+ self._view_statusbar_action.setShortcut("Ctrl+/")
+ self._view_statusbar_action.triggered.connect(self._toggle_statusbar)
+ view_menu.addAction(self._view_statusbar_action)
+ return view_menu
+
+ @Slot()
+ def _emit_dev_tools_requested(self):
+ tab = self.current_tab()
+ if tab:
+ tab.dev_tools_requested.emit(tab.page())
+
+ def create_window_menu(self, tabWidget):
+ menu = QMenu("Window")
+ self._next_tab_action = QAction("Show Next Tab", self)
+ shortcuts = []
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BraceRight))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_PageDown))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BracketRight))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Less))
+ self._next_tab_action.setShortcuts(shortcuts)
+ self._next_tab_action.triggered.connect(tabWidget.next_tab)
+
+ self._previous_tab_action = QAction("Show Previous Tab", self)
+ shortcuts.clear()
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BraceLeft))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_PageUp))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BracketLeft))
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Greater))
+ self._previous_tab_action.setShortcuts(shortcuts)
+ self._previous_tab_action.triggered.connect(tabWidget.previous_tab)
+
+ self._inspector_action = QAction("Open inspector in window", self)
+ shortcuts.clear()
+ shortcuts.append(QKeySequence(Qt.CTRL | Qt.SHIFT | Qt.Key_I))
+ self._inspector_action.setShortcuts(shortcuts)
+ self._inspector_action.triggered.connect(self._emit_dev_tools_requested)
+ self._window_menu = menu
+ menu.aboutToShow.connect(self._populate_window_menu)
+ return menu
+
+ def _populate_window_menu(self):
+ menu = self._window_menu
+ menu.clear()
+ menu.addAction(self._next_tab_action)
+ menu.addAction(self._previous_tab_action)
+ menu.addSeparator()
+ menu.addAction(self._inspector_action)
+ menu.addSeparator()
+ windows = self._browser.windows()
+ index = 0
+ title = self.window().windowTitle()
+ for window in windows:
+ action = menu.addAction(title, self.handle_show_window_triggered)
+ action.setData(index)
+ action.setCheckable(True)
+ if window == self:
+ action.setChecked(True)
+ index += 1
+
+ def create_help_menu(self):
+ help_menu = QMenu("Help")
+ help_menu.addAction("About Qt", qApp.aboutQt) # noqa: F821
+ return help_menu
+
+ @Slot()
+ def _back(self):
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.Back)
+
+ @Slot()
+ def _forward(self):
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.Forward)
+
+ @Slot()
+ def _stop_reload(self):
+ a = self._stop_reload_action.data()
+ self._tab_widget.trigger_web_page_action(QWebEnginePage.WebAction(a))
+
+ def create_tool_bar(self):
+ navigation_bar = QToolBar("Navigation")
+ navigation_bar.setMovable(False)
+ navigation_bar.toggleViewAction().setEnabled(False)
+
+ self._history_back_action = QAction(self)
+ back_shortcuts = remove_backspace(QKeySequence.keyBindings(QKeySequence.Back))
+
+ # For some reason Qt doesn't bind the dedicated Back key to Back.
+ back_shortcuts.append(QKeySequence(Qt.Key_Back))
+ self._history_back_action.setShortcuts(back_shortcuts)
+ self._history_back_action.setIconVisibleInMenu(False)
+ back_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoPrevious,
+ QIcon(":go-previous.png"))
+ self._history_back_action.setIcon(back_icon)
+ self._history_back_action.setToolTip("Go back in history")
+ self._history_back_action.triggered.connect(self._back)
+ navigation_bar.addAction(self._history_back_action)
+
+ self._history_forward_action = QAction(self)
+ fwd_shortcuts = remove_backspace(QKeySequence.keyBindings(QKeySequence.Forward))
+ fwd_shortcuts.append(QKeySequence(Qt.Key_Forward))
+ self._history_forward_action.setShortcuts(fwd_shortcuts)
+ self._history_forward_action.setIconVisibleInMenu(False)
+ next_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoNext,
+ QIcon(":go-next.png"))
+ self._history_forward_action.setIcon(next_icon)
+ self._history_forward_action.setToolTip("Go forward in history")
+ self._history_forward_action.triggered.connect(self._forward)
+ navigation_bar.addAction(self._history_forward_action)
+
+ self._stop_reload_action = QAction(self)
+ self._stop_reload_action.triggered.connect(self._stop_reload)
+ navigation_bar.addAction(self._stop_reload_action)
+
+ self._url_line_edit = QLineEdit(self)
+ self._fav_action = QAction(self)
+ self._url_line_edit.addAction(self._fav_action, QLineEdit.LeadingPosition)
+ self._url_line_edit.setClearButtonEnabled(True)
+ navigation_bar.addWidget(self._url_line_edit)
+
+ downloads_action = QAction(self)
+ downloads_action.setIcon(QIcon(":go-bottom.png"))
+ downloads_action.setToolTip("Show downloads")
+ navigation_bar.addAction(downloads_action)
+ dw = self._browser.download_manager_widget()
+ downloads_action.triggered.connect(dw.show)
+
+ return navigation_bar
+
+ def handle_web_action_enabled_changed(self, action, enabled):
+ if action == QWebEnginePage.Back:
+ self._history_back_action.setEnabled(enabled)
+ elif action == QWebEnginePage.Forward:
+ self._history_forward_action.setEnabled(enabled)
+ elif action == QWebEnginePage.Reload:
+ self._reload_action.setEnabled(enabled)
+ elif action == QWebEnginePage.Stop:
+ self._stop_action.setEnabled(enabled)
+ else:
+ print("Unhandled webActionChanged signal", file=sys.stderr)
+
+ def handle_web_view_title_changed(self, title):
+ off_the_record = self._profile.isOffTheRecord()
+ suffix = ("Qt Simple Browser (Incognito)" if off_the_record
+ else "Qt Simple Browser")
+ if title:
+ self.setWindowTitle(f"{title} - {suffix}")
+ else:
+ self.setWindowTitle(suffix)
+
+ def handle_new_window_triggered(self):
+ window = self._browser.create_window()
+ window._url_line_edit.setFocus()
+
+ def handle_new_incognito_window_triggered(self):
+ window = self._browser.create_window(True)
+ window._url_line_edit.setFocus()
+
+ def handle_file_open_triggered(self):
+ filter = "Web Resources (*.html *.htm *.svg *.png *.gif *.svgz);;All files (*.*)"
+ url, _ = QFileDialog.getOpenFileUrl(self, "Open Web Resource", "", filter)
+ if url:
+ self.current_tab().setUrl(url)
+
+ def handle_find_action_triggered(self):
+ if not self.current_tab():
+ return
+ search, ok = QInputDialog.getText(self, "Find", "Find:",
+ QLineEdit.Normal, self._last_search)
+ if ok and search:
+ self._last_search = search
+ self.current_tab().findText(self._last_search)
+
+ def closeEvent(self, event):
+ count = self._tab_widget.count()
+ if count > 1:
+ m = f"Are you sure you want to close the window?\nThere are {count} tabs open."
+ ret = QMessageBox.warning(self, "Confirm close", m,
+ QMessageBox.Yes | QMessageBox.No,
+ QMessageBox.No)
+ if ret == QMessageBox.No:
+ event.ignore()
+ return
+
+ event.accept()
+ self.about_to_close.emit()
+ self.deleteLater()
+
+ def tab_widget(self):
+ return self._tab_widget
+
+ def current_tab(self):
+ return self._tab_widget.current_web_view()
+
+ def handle_web_view_load_progress(self, progress):
+ if 0 < progress and progress < 100:
+ self._stop_reload_action.setData(QWebEnginePage.Stop)
+ self._stop_reload_action.setIcon(self._stop_icon)
+ self._stop_reload_action.setToolTip("Stop loading the current page")
+ self._progress_bar.setValue(progress)
+ else:
+ self._stop_reload_action.setData(QWebEnginePage.Reload)
+ self._stop_reload_action.setIcon(self._reload_icon)
+ self._stop_reload_action.setToolTip("Reload the current page")
+ self._progress_bar.setValue(0)
+
+ def handle_show_window_triggered(self):
+ action = self.sender()
+ if action:
+ offset = action.data()
+ window = self._browser.windows()[offset]
+ window.activateWindow()
+ window.current_tab().setFocus()
+
+ def handle_dev_tools_requested(self, source):
+ page = self._browser.create_dev_tools_window().current_tab().page()
+ source.setDevToolsPage(page)
+ source.triggerAction(QWebEnginePage.InspectElement)
+
+ def handle_find_text_finished(self, result):
+ sb = self.statusBar()
+ if result.numberOfMatches() == 0:
+ sb.showMessage(f'"{self._lastSearch}" not found.')
+ else:
+ active = result.activeMatch()
+ number = result.numberOfMatches()
+ sb.showMessage(f'"{self._last_search}" found: {active}/{number}')
+
+ def browser(self):
+ return self._browser