Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added gui/icons/grass/jupyter-inactive.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 101 additions & 0 deletions gui/icons/grass/jupyter-inactive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added gui/icons/grass/jupyter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 85 additions & 0 deletions gui/icons/grass/jupyter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions gui/wxpython/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ include $(MODULE_TOPDIR)/include/Make/Python.make
DSTDIR = $(GUIDIR)/wxpython

SRCFILES := $(wildcard icons/*.py xml/*) \
$(wildcard animation/*.py core/*.py datacatalog/*.py history/*.py dbmgr/*.py gcp/*.py gmodeler/*.py \
$(wildcard animation/*.py core/*.py datacatalog/*.py jupyter_notebook/*.py history/*.py dbmgr/*.py gcp/*.py gmodeler/*.py \
gui_core/*.py iclass/*.py lmgr/*.py location_wizard/*.py main_window/*.py mapwin/*.py mapdisp/*.py \
mapswipe/*.py modules/*.py nviz/*.py psmap/*.py rdigit/*.py \
rlisetup/*.py startup/*.py timeline/*.py vdigit/*.py \
Expand All @@ -19,7 +19,7 @@ SRCFILES := $(wildcard icons/*.py xml/*) \
DSTFILES := $(patsubst %,$(DSTDIR)/%,$(SRCFILES)) \
$(patsubst %.py,$(DSTDIR)/%.pyc,$(filter %.py,$(SRCFILES)))

PYDSTDIRS := $(patsubst %,$(DSTDIR)/%,animation core datacatalog history dbmgr gcp gmodeler \
PYDSTDIRS := $(patsubst %,$(DSTDIR)/%,animation core datacatalog jupyter_notebook history dbmgr gcp gmodeler \
gui_core iclass lmgr location_wizard main_window mapwin mapdisp modules nviz psmap \
mapswipe vdigit wxplot web_services rdigit rlisetup startup \
vnet timeline iscatt tplot photo2image image2target)
Expand Down
108 changes: 108 additions & 0 deletions gui/wxpython/jupyter_notebook/notebook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""
@package jupyter_notebook.notebook

@brief Manages the jupyter notebook widget.

Classes:
- page::JupyterAuiNotebook

(C) 2025 by the GRASS Development Team

This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.

@author Linda Karlovska <linda.karlovska seznam.cz>
"""

import wx
from wx.lib.agw import aui

try:
import wx.html2 as html # wx.html2 is available in wxPython 4.0 and later
except ImportError as e:
raise RuntimeError(_("wx.html2 is required for Jupyter integration.")) from e

from gui_core.wrap import SimpleTabArt


class JupyterAuiNotebook(aui.AuiNotebook):
def __init__(
self,
parent,
agwStyle=aui.AUI_NB_DEFAULT_STYLE
| aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
| aui.AUI_NB_TAB_EXTERNAL_MOVE
| aui.AUI_NB_BOTTOM
| wx.NO_BORDER,
):
"""
Wrapper for the notebook widget that manages notebook pages.
"""
self.parent = parent
self.webview = None

super().__init__(parent=self.parent, id=wx.ID_ANY, agwStyle=agwStyle)

self.SetArtProvider(SimpleTabArt())

self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnPageClose)

def _inject_javascript(self, event):
"""
Inject JavaScript into the Jupyter notebook page to hide UI elements.

Specifically hides:
- The File menu
- The top header bar

This is called once the WebView has fully loaded the Jupyter page.
"""
webview = event.GetEventObject()
js = """
var interval = setInterval(function() {
var fileMenu = document.querySelector('li#file_menu, a#filelink, a[aria-controls="file_menu"]');
if (fileMenu) {
if (fileMenu.tagName === "LI") {
fileMenu.style.display = 'none';
} else if (fileMenu.parentElement && fileMenu.parentElement.tagName === "LI") {
fileMenu.parentElement.style.display = 'none';
}
}
var header = document.getElementById('header-container');
if (header) {
header.style.display = 'none';
}
if (fileMenu && header) {
clearInterval(interval);
}
}, 500);
"""
webview.RunScript(js)

def AddPage(self, url, title):
"""
Add a new aui notebook page with a Jupyter WebView.
:param url: URL of the Jupyter file (str).
:param title: Tab title (str).
"""
browser = html.WebView.New(self)
wx.CallAfter(browser.LoadURL, url)
wx.CallAfter(browser.Bind, html.EVT_WEBVIEW_LOADED, self._inject_javascript)
super().AddPage(browser, title)

def OnPageClose(self, event):
"""Close the aui notebook page with confirmation dialog."""
index = event.GetSelection()
title = self.GetPageText(index)

dlg = wx.MessageDialog(
self,
message=_("Really close page '{}'?").format(title),
caption=_("Close page"),
style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
)

if dlg.ShowModal() != wx.ID_YES:
event.Veto()

dlg.Destroy()
Loading
Loading