Skip to content

Commit

Permalink
support ipypopout and give instructions what to install
Browse files Browse the repository at this point in the history
  • Loading branch information
maartenbreddels committed Dec 24, 2024
1 parent b3bfa58 commit 40c338e
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 11 deletions.
13 changes: 2 additions & 11 deletions jdaviz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,8 @@ def run_solara(host, port, theme, browser, production: bool = True):
server.serve_threaded()
server.wait_until_serving()
if browser == "qt":
from qtpy.QtWidgets import QApplication
from qtpy.QtWebEngineWidgets import QWebEngineView
from qtpy import QtCore

app = QApplication([""])
web = QWebEngineView()
web.resize(1000, 1000)
web.setUrl(QtCore.QUrl(server.base_url))
web.show()

app.exec_()
from . import qt
qt.run_qt(server.base_url)
else:
import webbrowser
controller = webbrowser.get(None if browser == 'default' else browser)
Expand Down
126 changes: 126 additions & 0 deletions jdaviz/qt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# this module is based on solara/server/qt.py
import sys
from typing import List
import webbrowser
try:
from qtpy.QtWidgets import QApplication
from qtpy.QtWebEngineWidgets import QWebEngineView
from qtpy.QtWebChannel import QWebChannel
from qtpy import QtCore, QtGui
except ModuleNotFoundError as e:
raise ModuleNotFoundError("""Qt browser requires Qt dependencies, run:
$ pip install jdaviz[qt]
to install.""") from e
import signal
from pathlib import Path

HERE = Path(__file__).parent


# setUrlRequestInterceptor, navigationRequested and acceptNavigationRequest
# all trigger the websocket to disconnect, so we need to block cross origin
# requests on the frontend/browser side by intercepting clicks on links

cross_origin_block_js = """
var script = document.createElement('script');
script.src = 'qrc:///qtwebchannel/qwebchannel.js';
document.head.appendChild(script);
script.onload = function() {
new QWebChannel(qt.webChannelTransport, function(channel) {
let py_callback = channel.objects.py_callback;
document.addEventListener('click', function(event) {
let target = event.target;
while (target && target.tagName !== 'A') {
target = target.parentNode;
}
if (target && target.tagName === 'A') {
const linkOrigin = new URL(target.href).origin;
const currentOrigin = window.location.origin;
if (linkOrigin !== currentOrigin) {
event.preventDefault();
console.log("Blocked cross-origin navigation to:", target.href);
py_callback.open_link(target.href); // Call Python method
}
}
}, true);
});
};
"""


class PyCallback(QtCore.QObject):
@QtCore.Slot(str)
def open_link(self, url):
webbrowser.open(url)


class QWebEngineViewWithPopup(QWebEngineView):
# keep a strong reference to all windows
windows: List = []

def __init__(self):
super().__init__()
self.page().newWindowRequested.connect(self.handle_new_window_request)

# Set up WebChannel and py_callback object
self.py_callback = PyCallback()
self.channel = QWebChannel()
self.channel.registerObject("py_callback", self.py_callback)
self.page().setWebChannel(self.channel)

self.loadFinished.connect(self._inject_javascript)

def _inject_javascript(self, ok):
self.page().runJavaScript(cross_origin_block_js)

def handle_new_window_request(self, info):
webview = QWebEngineViewWithPopup()
geometry = info.requestedGeometry()
width = geometry.width()
parent_size = self.size()
if width == 0:
width = parent_size.width()
height = geometry.height()
if height == 0:
height = parent_size.height()
print("new window", info.requestedUrl(), width, height)
webview.resize(width, height)
webview.setUrl(info.requestedUrl())
webview.show()
QWebEngineViewWithPopup.windows.append(webview)
return webview


def run_qt(url, app_name="Jdaviz"):
app = QApplication([])
web = QWebEngineViewWithPopup()
web.setUrl(QtCore.QUrl(url))
web.resize(1024, 1024)
web.show()

app.setApplicationDisplayName(app_name)
app.setApplicationName(app_name)
web.setWindowTitle(app_name)
app.setWindowIcon(QtGui.QIcon(str(HERE / "data/icons/imviz_icon.svg")))
if sys.platform.startswith("darwin"):
# Set app name, if PyObjC is installed
# Python 2 has PyObjC preinstalled
# Python 3: pip3 install pyobjc-framework-Cocoa
try:
from Foundation import NSBundle

bundle = NSBundle.mainBundle()
if bundle:
app_info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
if app_info is not None:
app_info["CFBundleName"] = app_name
app_info["CFBundleDisplayName"] = app_name
except ModuleNotFoundError:
pass

# without this, ctrl-c does not work in the terminal
signal.signal(signal.SIGINT, signal.SIG_DFL)
app.exec_()

0 comments on commit 40c338e

Please sign in to comment.