From 2897f79504fa159e31f5890d6f7414e20fac6107 Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Sat, 9 Jan 2021 17:33:49 +0000 Subject: [PATCH] Widget: Display page in external browser when link in notebook is clicked When the user clicks on a link in a notebook, the NotebookWidget calls createWindow() in order to open a new tab to display the link. This commit intercepts the call and creates a WebViewInBrowser object. When the URL in that object is set, the link is opened in an external web browser. --- spyder_notebook/widgets/client.py | 51 ++++++++++++++++++-- spyder_notebook/widgets/tests/test_client.py | 15 ++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/spyder_notebook/widgets/client.py b/spyder_notebook/widgets/client.py index 7d81c736..ae25a925 100644 --- a/spyder_notebook/widgets/client.py +++ b/spyder_notebook/widgets/client.py @@ -5,9 +5,10 @@ """Qt widgets for the notebook.""" +# Standard library imports +import json import os import os.path as osp -import json from string import Template import sys @@ -19,10 +20,8 @@ from qtpy.QtWidgets import (QApplication, QMenu, QVBoxLayout, QWidget, QMessageBox) -# Notebook imports -from notebook.utils import url_path_join, url_escape - # Third-party imports +from notebook.utils import url_path_join, url_escape import requests # Spyder imports @@ -55,6 +54,39 @@ # ----------------------------------------------------------------------------- # Widgets # ----------------------------------------------------------------------------- +class WebViewInBrowser(QWebEngineView): + """ + WebView which opens document in an external browser. + + This is a subclass of QWebEngineView, which as soon as the URL is set, + opens the web page in an external browser and closes itself. It is used + in NotebookWidget to open links. + """ + + def __init__(self, parent): + """Construct object.""" + super().__init__(parent) + self.urlChanged.connect(self.open_in_browser) + + def open_in_browser(self, url): + """ + Open web page in external browser and close self. + + Parameters + ---------- + url : QUrl + URL of web page to open in browser + """ + import webbrowser + try: + webbrowser.open(url.toString()) + except ValueError: + # See: spyder-ide/spyder#9849 + pass + self.stop() + self.close() + + class NotebookWidget(DOMWidget): """WebView widget for notebooks.""" @@ -153,6 +185,17 @@ def show_message(self, page): """Show a message page with the given .html file.""" self.setHtml(page) + def createWindow(self, webWindowType): + """ + Create new browser window. + + This function is called by Qt if the user clicks on a link in the + notebook. The goal is to open the web page in an external browser. + To that end, we create and return an object which will open the browser + when Qt sets the URL. + """ + return WebViewInBrowser(self.parent()) + class NotebookClient(QWidget): """ diff --git a/spyder_notebook/widgets/tests/test_client.py b/spyder_notebook/widgets/tests/test_client.py index de4f8c13..b14848f4 100644 --- a/spyder_notebook/widgets/tests/test_client.py +++ b/spyder_notebook/widgets/tests/test_client.py @@ -7,6 +7,7 @@ # Third-party imports import pytest +from qtpy.QtCore import QUrl from qtpy.QtWidgets import QWidget import requests @@ -47,6 +48,20 @@ def plugin(plugin_without_server): return plugin_without_server +def test_notebookwidget_create_window(plugin, mocker, qtbot): + """Test that NotebookWidget.create_window() creates an object that calls + webbrowser.open() when its URL is set.""" + widget = plugin.client.notebookwidget + mock_notebook_open = mocker.patch('webbrowser.open') + new_view = widget.createWindow(42) + url = 'https://www.spyder-ide.org/' + + with qtbot.waitSignal(new_view.loadFinished): + new_view.setUrl(QUrl(url)) + + mock_notebook_open.assert_called_once_with(url) + + def test_notebookclient_get_kernel_id(plugin, mocker): """Basic unit test for NotebookClient.get_kernel_id().""" response = mocker.Mock()